Appearance
常见故障排查
MySQL 故障排查的核心不是背一堆命令,而是遇到问题时知道从哪几个方向切进去:是启动不起来、连不上、跑得慢、空间满还是复制断了。错误日志通常是第一站。
慢查询和锁等待排查会反复用到索引、执行计划和 InnoDB 内部状态,相关基础见 索引与执行计划 和 InnoDB 与性能调优。
一、错误日志
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 的状态更具体。比如 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.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 是否在授权规则的网段内。
四、连接数耗尽
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_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 或代码要怎么改。如果复盘记录里只有一句"数据库慢",下一次同样问题再发生时,排查仍然是重新开始。