Skip to content

常见故障排查

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

一、先找到错误日志在哪

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 的状态更具体。启动失败、崩溃恢复、复制断掉、权限错误——不看错误日志等于猜谜。

二、MySQL 起不来

最常见的几类原因:

现象日志关键信息排查方向
配置参数写错了unknown variable检查 my.cnf 有没有拼错的参数名
目录权限不对Permission denieddatadir、log、socket 目录的属主和权限
数据目录不存在datadir 相关错误路径有没有写错、挂载有没有掉
端口被占用bind on TCP/IP port failed旧实例残留、其他服务占用了 3306
磁盘满了No space left on devicedf -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.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 是否在授权规则的网段内。

四、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_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 或代码要怎么改。如果复盘记录里只有一句"数据库慢",下一次同样问题再发生时,排查仍然是重新开始。