Appearance
常见故障排查
MySQL 故障排查的核心不是背一堆命令,而是遇到问题时知道从哪几个方向切进去:是启动不起来、连不上、跑得慢、空间满还是复制断了。错误日志通常是第一站。
一、先找到错误日志在哪
sql
SHOW VARIABLES LIKE 'log_error';bash
tail -n 200 /data/mysql/logs/error.logsystemd 管理下也可以看 journal:
bash
systemctl status mysqld --no-pager
journalctl -u mysqld -n 100 --no-pager错误日志通常比 systemd 的状态更具体。启动失败、崩溃恢复、复制断掉、权限错误——不看错误日志等于猜谜。
二、MySQL 起不来
最常见的几类原因:
| 现象 | 日志关键信息 | 排查方向 |
|---|---|---|
| 配置参数写错了 | unknown variable | 检查 my.cnf 有没有拼错的参数名 |
| 目录权限不对 | Permission denied | datadir、log、socket 目录的属主和权限 |
| 数据目录不存在 | datadir 相关错误 | 路径有没有写错、挂载有没有掉 |
| 端口被占用 | bind on TCP/IP port failed | 旧实例残留、其他服务占用了 3306 |
| 磁盘满了 | No space left on device | df -h 看磁盘、df -ih 看 inode |
| redo/undo 文件损坏 | InnoDB 初始化错误 | 日志里会有更具体的原因 |
确认配置文件里的关键路径:
bash
grep -E 'datadir|log_error|socket|pid-file|tmpdir' /etc/my.cnf确认目录权限和属主:
bash
ls -ld /data/mysql /data/mysql/data /data/mysql/logs /data/mysql/tmp
chown -R mysql:mysql /data/mysql递归改属主时范围要收住——只在 MySQL 实际使用的目录上操作。对整个系统目录做递归 chown 的后果比 MySQL 起不来严重得多。
检查 3306 端口有没有被占用:
bash
ss -lntp | grep ':3306'如果已经有 mysqld 进程在监听,确认是不是旧实例没停或启动了两次。
三、连不上 MySQL
| 报错 | 大概率是什么问题 |
|---|---|
Can't connect to MySQL server on 'xxx' | 网络不通、端口没监听、防火墙拦截、MySQL 没启动 |
Access denied for user 'xxx'@'xxx' | 用户不存在、密码错、host 不匹配、账号被锁定 |
Too many connections | 连接数用完了 |
Lost connection during query | 网络中断、超时、或 MySQL 在执行期间崩溃重启 |
从排查工具链看:
bash
# 网络/端口是否可达
nc -vz mysql.example.com 3306
# MySQL 是否在监听
ss -lntp | grep ':3306'
# 本机用 socket 能不能连(绕开网络层)
mysql -uroot -p --socket=/data/mysql/tmp/mysql.socksocket 能连但 TCP 不能连,看 bind_address 配置——是不是只绑了 127.0.0.1:
sql
SHOW VARIABLES LIKE 'bind_address';端口通但 Access denied,进数据库确认账号状态:
sql
SELECT user, host, plugin, account_locked
FROM mysql.user
WHERE user = 'app_user';
SELECT USER(), CURRENT_USER();看到 account_locked = Y 就解锁;看到 host 不匹配就确认客户端的来源 IP 是否在授权规则的网段内。
四、Too many connections
text
ERROR 1040 (HY000): Too many connections此时如果还能登录(保留了一个管理连接),先看连接分布:
sql
-- 看总量
SHOW VARIABLES LIKE 'max_connections';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
-- 按来源聚合
SELECT user, host, db, command, COUNT(*) AS cnt
FROM information_schema.processlist
GROUP BY user, host, db, command
ORDER BY cnt DESC;临时调大上限——但重启会丢,只做应急:
sql
SET GLOBAL max_connections = 1000;然后按现象分类处理:
| 堆积类型 | 临时处理 | 后续修补 |
|---|---|---|
| Sleep 连接很多 | 联系应用缩连接池,必要时清理异常 Sleep | 检查连接池大小和空闲释放策略 |
| Query 状态且耗时长 | 找到慢 SQL,必要时限流 | SQL 调优、加索引 |
| lock wait 状态 | 找到阻塞源,确认后 kill | 缩短事务、固定更新顺序 |
| 某个 host 连接暴涨 | 摘除或限流那个来源,确认是不是重试风暴 | 检查发布变更、连接泄漏、故障恢复逻辑 |
KILL 连接前要了解线程在做什么。杀一个正在执行大事务的线程,回滚过程可能持续数分钟,期间继续占资源。杀业务正常请求,应用可能立即重试,等于没杀。
五、磁盘空间问题
bash
df -h
df -ih # inode 要用完时空间没满也会出问题
du -sh /data/mysql/*MySQL 里容易膨胀的大文件:
| 文件 | 为什么会长大 |
|---|---|
| binlog | 没有过期清理或清理时间设太长 |
| error log / slow log | 日志量大且没有 logrotate |
| ibdata 共享表空间 | 历史遗留,或 innodb_file_per_table=OFF |
| ibd 独立表空间 | 数据本身增长 |
| undo 表空间 | 长事务拖住 undo 清理,purge 跟不上 |
看 binlog 列表和清理策略:
sql
SHOW BINARY LOGS;
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';手动清理不再需要的旧 binlog:
sql
PURGE BINARY LOGS BEFORE '2026-05-01 00:00:00';手动清理前要确认:主从环境下,从库是否已经读完这些 binlog;备份恢复时,这些 binlog 是否还链接着全量备份。删掉从库还没拉到的 binlog,复制立刻中断。
设置自动清理周期,例如保留 7 天(604800 秒):
sql
SET PERSIST binlog_expire_logs_seconds = 604800;SET PERSIST 是 MySQL 8.0 的持久化变量——既对当前实例生效,也写入 mysqld-auto.cnf,重启后保留。不支持的版本写到配置文件 my.cnf 里。
六、字符集导致的乱码和报错
字符集问题是排查时容易被忽略的一个方向——看起来数据写进去了,查出来却是乱码,或者 emoji 插入直接报错。
sql
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';确认某个库、某张表用的是什么字符集:
sql
SHOW CREATE DATABASE app_db\G
SHOW CREATE TABLE app_db.orders\G常见的字符集问题路径:
| 现象 | 原因 | 解决 |
|---|---|---|
| 中文变乱码 | 客户端/连接/表的字符集不一致 | 统一为 utf8mb4 |
emoji 插入报 Incorrect string value | 字符集是 latin1 或 utf8(非 mb4) | 改成 utf8mb4 |
| 排序结果不符合预期 | collation 不是想要的排序规则 | 确认区分重音、大小写需求 |
| mysqldump 导入后数据乱码 | 导出时未指定字符集 | 导出导入都加 --default-character-set=utf8mb4 |
MySQL 里的 utf8 历史上只是 UTF-8 的一个子集(最多 3 字节,不支持 emoji 等补充字符),utf8mb4 才是完整的 UTF-8。建新库时直接用 utf8mb4,避免后面再折腾转换。
七、崩溃恢复和表损坏
InnoDB 在 MySQL 异常退出后会做 crash recovery——根据 redo 重放已提交的事务、根据 undo 回滚未提交的事务。这个过程在启动时自动执行,错误日志里能看到类似 "Starting crash recovery" 的记录。
崩溃恢复和 binlog 是两套不同的机制。前者解决数据库宕机后实例的自动恢复,后者解决主从复制和时间点恢复。一个事务被正常提交并删除了数据,InnoDB 不会把它当作"故障"来恢复——重新启动后数据还是被删掉的状态。
CHECK TABLE 可以检查表的完整性:
sql
CHECK TABLE app_db.orders;InnoDB 表损坏时,和 MyISAM 时代的 REPAIR TABLE 思路不同。严重损坏通常从备份恢复,或者按错误日志里的具体提示决定是否能用 innodb_force_recovery 导出数据。
innodb_force_recovery 是救数据用的参数,值从 1 到 6,级别越高限制越多(禁止后台操作、禁止 INSERT/UPDATE 等)。使用前务必保留一份当前数据目录的完整副本——这是不可逆操作。
八、复制异常(初查)
sql
SHOW REPLICA STATUS\G旧版本可能是 SHOW SLAVE STATUS\G。
重点看这几个字段:
| 字段 | 正常应该是什么 |
|---|---|
Replica_IO_Running | Yes——从库在拉主库的 binlog |
Replica_SQL_Running | Yes——从库在执行拉到的日志 |
Last_IO_Error | 空——没有 IO 层面的错误 |
Last_SQL_Error | 空——没有 SQL 执行错误 |
Seconds_Behind_Source | 尽量小——从库延迟 |
常见复制异常的类型:
| 错误表现 | 常见原因 |
|---|---|
| IO 线程连不上主库 | 网络不通、复制账号密码错、防火墙拦截 |
| binlog 找不到 | 主库清理了从库还没读到的 binlog 文件 |
| duplicate key | 从库上已经有了主库要插入的行(数据不一致) |
| table doesn't exist | 主库上建的表在从库上不存在(DDL 没同步过来) |
处理复制异常时先保存 SHOW REPLICA STATUS\G 的完整输出和错误日志,再判断是跳过、修数据、重搭从库还是从备份恢复。RESET REPLICA 会清掉复制配置和状态,要小心。
九、慢查询现场处理
一条慢 SQL 的完整判断链路——不只是看 SQL 本身:
| 慢日志字段 | 提示什么 |
|---|---|
Lock_time 高 | 先看锁等待,再看索引,卡住的原因可能不是查询本身 |
Rows_examined >> Rows_sent | 访问路径可能没走合适的索引 |
Rows_sent 很大 | SQL 可能没问题,是业务取了太多数据 |
Query_time 长但 rows 很少 | 可能是磁盘 IO 慢、锁等待或网络问题 |
现场看当前正在执行的 SQL 状态:
sql
SELECT id, user, host, db, command, time, state, info
FROM information_schema.processlist
WHERE command <> 'Sleep'
ORDER BY time DESC
LIMIT 20;拿到具体的慢 SQL,接 EXPLAIN 看执行计划、看 key 命中了没有、rows 预估多少、Extra 里有没有 using filesort 或 using temporary。
故障处理现场的总体策略——先止血,再排查根因:
| 处理动作 | 什么时候做 |
|---|---|
| 补索引 | SQL 的 WHERE/ORDER BY 模式稳定,扫描行数可以明显减少 |
| 更新统计信息 | 表数据分布变化后优化器选错索引 |
| kill 阻塞源 | 确认是异常长事务阻塞大量正常请求 |
| 限流/入口限制 | SQL 已拖垮连接数,先让数据库恢复响应 |
| 调大 Buffer Pool | 确实内存有余,物理读占比高 |
处理之后立刻看几件事确认效果:慢日志是不是还在刷、锁等待状态有没有消失、连接数是否回落、应用错误率是否恢复。数字下来了才算有效。
十、故障现场保存
出了事不能只靠记忆复盘。保留现场:
bash
mkdir -p /tmp/mysql-incident-$(date +%F-%H%M%S)
cd /tmp/mysql-incident-*
date > date.txt
df -h > df-h.txt
free -h > free-h.txt
iostat -x 1 5 > iostat.txt
tail -n 500 /data/mysql/logs/error.log > error-last-500.logMySQL 层能抓的现场:
sql
-- 保存到文本(客户端操作)
SHOW FULL PROCESSLIST\G
SHOW ENGINE INNODB STATUS\G
SHOW REPLICA STATUS\G采集文件可能包含 SQL 内容、库名账号名和 IP 地址,处理和分享前留意敏感信息。
复盘记录至少包括:故障时间、具体 SQL 和执行计划、当时的连接和锁状态、现场做了什么(加索引/kill/限流等)、后续 SQL 或代码要怎么改。如果复盘记录里只有一句"数据库慢",下一次同样问题再发生时,排查仍然是重新开始。