Skip to content

常见故障排查

MySQL 故障排查的核心不是背一堆命令,而是遇到问题时知道从哪几个方向切进去:是启动不起来、连不上、跑得慢、空间满还是复制断了。错误日志通常是第一站。

慢查询和锁等待排查会反复用到索引、执行计划和 InnoDB 内部状态,相关基础见 索引与执行计划InnoDB 与性能调优

一、错误日志

sql
SHOW VARIABLES LIKE 'log_error';
bash
tail -n 200 /data/mysql/logs/error.log

systemd 管理下也可以看 journal:

bash
systemctl status mysqld --no-pager
journalctl -u mysqld -n 100 --no-pager

错误日志通常比 systemd 的状态更具体。比如 systemd 只显示 mysqld.service: Failed with result 'exit-code',错误日志里却能看到 unknown variable 'innodb_buffer_pool_szie',基本就能确定是配置项拼错。启动失败、崩溃恢复、复制断掉、权限错误,第一站都是错误日志。

二、启动失败

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 进程在监听,确认是不是旧实例没停或启动了两次。

三、连接失败

几类常见报错和大概率原因:Can't connect to MySQL server 是网络不通、端口没监听、防火墙拦截或 MySQL 没启动;Access denied for user 是用户不存在、密码错、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.sock

socket 能连但 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 是否在授权规则的网段内。

四、连接数耗尽

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 不会把它当作"故障"来恢复——重新启动后数据还是被删掉的状态。所以别指望 crash recovery 能救回误删的数据,那得靠备份和 binlog。

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_RunningYes——从库在拉主库的 binlog
Replica_SQL_RunningYes——从库在执行拉到的日志
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.log

MySQL 层能抓的现场:

sql
-- 保存到文本(客户端操作)
SHOW FULL PROCESSLIST\G
SHOW ENGINE INNODB STATUS\G
SHOW REPLICA STATUS\G

采集文件可能包含 SQL 内容、库名账号名和 IP 地址,处理和分享前留意敏感信息。

复盘记录至少包括:故障时间、具体 SQL 和执行计划、当时的连接和锁状态、现场做了什么(加索引/kill/限流等)、后续 SQL 或代码要怎么改。如果复盘记录里只有一句"数据库慢",下一次同样问题再发生时,排查仍然是重新开始。