• 2026年7月15日
  • 5 分钟阅读
  • SystemDo

MySQL 数据库常见故障怎么排查?问题定位与维护清单

系统化梳理 MySQL 常见故障类型、排查顺序、证据保留办法与长期治理清单,帮助运维与开发团队减少停机时间,提升数据库稳定性。

MySQL 数据库常见故障怎么排查?问题定位与维护清单
SystemDo
SystemDo

软件定制开发团队

"真正有价值的技术内容,应该能帮助客户更快判断方向、预算和落地路径。"

故障分类:先判断是哪个层面的问题

处理 MySQL 故障的第一步不是翻日志,而是确认问题属于哪个层面。根据过去十年参与的企业项目经验,绝大多数故障可以归入以下五类:

  • **连接层故障**:客户端无法建立连接、连接数打满、认证失败。
  • **查询层故障**:慢查询、锁等待、死锁、查询结果错误。
  • **存储层故障**:磁盘写满、表损坏、ibdata 文件异常增长。
  • **复制层故障**:主从延迟、从库 SQL 线程停止、数据不一致。
  • **实例层故障**:OOM 被杀、mysqld 崩溃、启动失败。

不同层面的排查入口差异很大。连接层先看 error log 和 netstat;查询层先看 slow log 和 processlist;存储层先看磁盘使用和系统表。遇到故障时,先问一句:这是连接不上,还是连上了跑得慢,还是数据读不出来?这个判断决定了后续 80% 的排查效率。

排查顺序:从外到内,先止血再根治

当故障正在发生时,时间窗口非常有限。我的建议是遵循“从外到内、先止血再根治”的顺序。

第一优先级:恢复服务

无论原因是什么,如果业务已经中断,优先恢复服务。常见做法:

  • 连接数打满时,kill 掉 idle 连接或重启应用连接池。
  • 主库故障时,执行 failover 切换到从库。
  • 磁盘满时,清理 binlog 或迁移数据文件到新盘。
  • 实例崩溃时,启动备用实例或从备份恢复。

这个阶段不要花时间分析根因。记录现场、保留证据、快速恢复,是唯一正确的做法。

第二优先级:收集现场证据

服务恢复后,立即收集以下信息。如果等到第二天再查,很多线索已经消失。

  • **操作系统层面**:dmesg 检查 OOM 记录、top 看 CPU/内存峰值、iostat 看磁盘 IO 延迟、df 看磁盘使用率。
  • **MySQL 层面**:SHOW ENGINE INNODB STATUS 输出(重点看 LATEST DETECTED DEADLOCK 和 TRANSACTIONS 段)、SHOW FULL PROCESSLIST 的输出、slow log 中故障时间段的记录、error log 全文。
  • **网络层面**:应用服务器到数据库的 ping 延迟和丢包率、tcpdump 抓包(如果怀疑连接中断或超时)。
  • **应用层面**:应用日志中数据库连接超时或查询报错的时间戳和 SQL。

这些证据一旦丢失,后续分析就只能靠猜。建议将收集脚本固化到运维工具中,故障时一键执行。

第三优先级:分析根因

拿到证据后,按以下顺序排查:

1. 检查 error log 中是否有 panic、assertion failure 或 InnoDB 损坏信息。
2. 检查 slow log,找到故障时间段的 TOP N 慢查询。
3. 检查 processlist,看是否有长时间未提交的事务或锁等待。
4. 检查系统指标,看是否在故障前有内存、IO 或连接数的异常增长。
5. 检查主从状态,看延迟是否突然增大或复制线程是否停止。

一个常见误区是直接翻慢查询日志,但实际根因可能是磁盘 IO 被打满而非 SQL 本身有问题。所以排查顺序必须从系统层到数据库层再到 SQL 层。

连接故障:超时与连接数打满

连接故障是最常见的 MySQL 故障之一,表现形式是应用报“Can't connect to MySQL server”或“Too many connections”。

常见原因

  • 应用连接池配置过大,超过 max_connections。
  • 应用没有正确关闭连接,导致连接泄漏。
  • 网络防火墙或安全组规则变动,导致端口不通。
  • MySQL 实例所在服务器内存不足,触发 OOM killer 杀掉 mysqld。
  • bind-address 配置错误,只允许本地连接。

排查方法

先确认 mysqld 是否存活:ps aux | grep mysqld。如果进程不存在,检查 dmesg 看是否被 OOM killer 杀掉。如果进程存在但无法连接,检查端口监听状态:netstat -tlnp | grep 3306。如果端口正常,检查 max_connections 和当前连接数:

```
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
```

如果 Threads_connected 接近 max_connections,说明连接数打满。此时可以临时增加 max_connections,但根本办法是排查连接泄漏。如果连接数正常但应用仍然报错,检查应用服务器到数据库的网络连通性,telnet 数据库IP 3306 看是否通。不通则检查防火墙和安全组。

预防措施

  • 设置合理的 max_connections,一般为 200-500,视服务器配置调整。
  • 应用层使用连接池并设置连接超时和空闲回收。
  • 监控 Threads_connected 和 Aborted_connects 指标。
  • 为运维账号预留一个连接通道(如 admin 账号单独设置 max_user_connections)。

慢查询:排查顺序比优化更重要

慢查询的排查经常陷入“上来就加索引”的误区。实际上,慢查询的根因可能是索引失效、表数据量过大、服务器资源不足、锁竞争或者 SQL 写法问题。

排查顺序

1. 确认慢查询日志已开启,并设置 long_query_time 为 1 秒或更低。
2. 在故障时间段内,从 slow log 中提取 TOP 5 慢查询。
3. 对每条慢查询执行 EXPLAIN,检查 type、rows、Extra 字段。
4. 检查表的行数和数据分布:SHOW TABLE STATUS 看行数,SHOW INDEX 看索引基数。
5. 检查服务器资源:CPU 是否打满、磁盘 IO 是否高、内存是否不足导致排序使用磁盘临时表。

常见慢查询类型与处理

  • **全表扫描**:EXPLAIN 中 type=ALL。通常缺少索引或索引被函数/隐式转换导致失效。优先考虑添加合适索引。
  • **临时表排序**:EXPLAIN 中 Extra=Using temporary; Using filesort。常见于 GROUP BY 或 ORDER BY 没有走索引。可以调整排序缓冲区或优化 SQL 避免排序。
  • **大表 LIMIT 深分页**:SELECT * FROM t ORDER BY id LIMIT 100000, 20。这种查询即使有索引也会扫描大量行。改为基于游标的分页:WHERE id > 100000 LIMIT 20。
  • **锁等待**:慢查询本身不慢,但被其他事务锁住。检查 processlist 中是否有长时间未提交的写事务。

什么情况下加索引无效

  • 查询返回的数据量超过表行数的 20%,优化器可能放弃索引。
  • 索引列上有函数操作,如 WHERE DATE(create_time) = '2026-07-14'。
  • 索引列参与隐式类型转换,如 WHERE varchar_col = 123。
  • 索引列选择性太低,如性别字段。

遇到这些情况,需要从业务逻辑或 SQL 写法角度优化,而不是堆索引。

死锁:不是所有死锁都需要改代码

死锁在 InnoDB 引擎下是正常现象,事务并发越高,死锁概率越大。MySQL 会自动检测死锁并回滚其中一个事务,应用层只需要捕获死锁异常并重试。

什么情况下需要关注死锁

  • 死锁频率过高,导致大量事务重试,影响吞吐量。
  • 死锁导致主从复制中断(从库 SQL 线程因死锁停止)。
  • 应用层没有实现重试机制,死锁直接报错给用户。

排查死锁

使用 SHOW ENGINE INNODB STATUS 查看 LATEST DETECTED DEADLOCK 段。该段会显示两个事务各自持有的锁和等待的锁,以及回滚的事务。

典型的死锁场景是:事务 A 更新行 1,事务 B 更新行 2,然后事务 A 等待行 2,事务 B 等待行 1。排查时关注两条 SQL 的访问顺序是否一致。

预防死锁

  • 所有事务按照相同的顺序访问表或行。
  • 缩短事务执行时间,减少锁持有时间。
  • 合理设置隔离级别,READ COMMITTED 比 REPEATABLE READ 死锁概率低。
  • 对于高并发写入场景,考虑使用乐观锁或队列化写入。

主从延迟:区分正常延迟与异常延迟

主从延迟是 MySQL 复制架构中最常见的故障之一。延迟分为正常延迟和异常延迟,处理方式完全不同。

正常延迟

主库写入量超过从库单线程(或单 worker)的回放能力。在 MySQL 5.7 及以下版本中,从库默认单线程回放 relay log,如果主库写入量大,延迟是必然的。解决方案:

  • 升级到 MySQL 8.0,开启并行复制(slave_parallel_workers > 0)。
  • 将不需要实时一致性的读请求分流到从库。
  • 对于核心业务,使用半同步复制减少数据丢失风险。

异常延迟

从库 SQL 线程因错误或锁等待停止。排查步骤:

1. SHOW SLAVE STATUS\G,检查 Slave_IO_Running 和 Slave_SQL_Running 是否为 Yes。
2. 如果 SQL 线程为 No,查看 Last_Error 字段。
3. 常见错误:主键冲突、表不存在、表结构不一致、死锁。
4. 根据错误类型处理:跳过错误(SET GLOBAL sql_slave_skip_counter = 1)或手动修复数据。

数据一致性检查

主从延迟会导致从库读到旧数据,但不会导致数据不一致。数据不一致通常由以下原因引起:

  • 从库执行了写操作(人为或应用误操作)。
  • 主库使用非事务引擎(如 MyISAM)或非确定性的语句。
  • 使用基于语句的复制(STATEMENT)且 SQL 使用了 NOW() 等非确定性函数。

定期使用 pt-table-checksum 和 pt-table-sync 检查并修复主从数据一致性,建议每周一次。

磁盘写满:最容易被忽视的故障

磁盘写满导致的故障通常表现为:应用报错“The table 'xxx' is full”、无法写入数据、mysqld 启动失败。

排查步骤

1. df -h 查看磁盘使用率。
2. du -sh /var/lib/mysql 查看数据库目录大小。
3. 进入目录后使用 du -sh * 找出最大的子目录或文件。
4. 如果是 binlog 占用过多,检查 binlog 过期时间:SHOW VARIABLES LIKE 'expire_logs_days'。如果设置为 0 或过大,手动清理:PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY。
5. 如果是 ibdata1 文件过大,需要确认是否开启了 innodb_file_per_table。如果没有开启,所有表的数据和索引都存储在共享表空间中,无法收缩。唯一的办法是重建实例。
6. 如果是 slow log 或 error log 过大,检查日志轮转配置。

预防措施

  • 监控磁盘使用率,设置 80% 告警。
  • 设置合理的 binlog 过期时间,建议 3-7 天。
  • 从 MySQL 5.6 开始默认开启 innodb_file_per_table,如果还在使用老版本,建议迁移。
  • 定期清理 slow log 和 error log,使用 logrotate 或 cron 脚本。

实例崩溃:OOM 与 mysqld 被杀

实例崩溃是最严重的故障之一,通常由内存不足或内核 bug 引起。

OOM 原因

  • innodb_buffer_pool_size 设置过大,超过物理内存。
  • 多个 MySQL 实例或数据库进程共享同一台服务器。
  • 内存泄漏(较少见,通常由 MySQL 版本 bug 引起)。
  • 服务器本身内存不足。

排查步骤

1. 检查 dmesg 是否有 oom-killer 记录。
2. 检查 MySQL error log 是否有 crash 信息。
3. 检查系统日志 /var/log/messages 或 journalctl。
4. 如果崩溃前有大量慢查询或临时表操作,可能是临时表使用了磁盘但内存分配不合理。

恢复与预防

  • 调整 innodb_buffer_pool_size 为物理内存的 60%-70%。
  • 设置 max_connections 和 thread_cache_size 防止连接数过高。
  • 使用 cgroups 或 systemd 限制 MySQL 进程的内存使用。
  • 升级到稳定的 MySQL 版本,避免已知的 crash bug。

证据保留:建立标准化的故障采集流程

很多团队在故障恢复后才发现,该留的证据没留。建议建立标准化的故障采集脚本,故障时一键执行。脚本应包含以下内容:

```

  • 系统层面:dmesg, top, iostat, df, free, netstat
  • MySQL 层面:SHOW ENGINE INNODB STATUS, SHOW FULL PROCESSLIST, SHOW SLAVE STATUS
  • 日志文件:error log, slow log, binlog 索引
  • 配置信息:my.cnf, SHOW VARIABLES

```

将脚本保存为 troubleshoot.sh,故障时执行并将输出重定向到文件。这样即使事后需要分析,也有完整的上下文。

在 SystemDo 参与的一个金融项目中,我们正是靠这套标准化流程,在数据库崩溃后 10 分钟内完成了证据采集,避免了因证据丢失导致的三天排查拉锯战。

长期治理:从救火到防火

故障排查的最终目标是减少故障发生频率。以下是一份经过多个项目验证的长期治理清单:

监控与告警

  • 数据库关键指标:QPS、TPS、连接数、慢查询数、死锁数、主从延迟。
  • 系统指标:CPU、内存、磁盘 IO、磁盘使用率。
  • 设置告警阈值,并确保告警能触达到值班人员。

定期巡检

  • 每周:检查主从复制状态、慢查询日志、磁盘使用率。
  • 每月:检查表碎片、索引使用情况、数据一致性。
  • 每季度:检查 MySQL 版本更新、安全补丁、配置参数优化。

备份与恢复演练

  • 每天全量备份 + binlog 增量备份。
  • 每季度执行一次恢复演练,验证备份可用性。
  • 演练时模拟主库故障、磁盘损坏、误删数据等场景。

变更管理

  • 所有 DDL 操作(ALTER TABLE、CREATE INDEX)必须在业务低峰期执行。
  • DDL 前备份表结构,DDL 后观察慢查询变化。
  • 配置变更(my.cnf 参数调整)需要先在小流量实例上验证。

知识沉淀

  • 每次故障处理完成后,编写故障报告,包含故障时间、影响范围、根因、处理过程、改进措施。
  • 将故障报告纳入团队知识库,定期复盘。

总结

MySQL 故障排查不是一门玄学,而是一套可以标准化、流程化的工程实践。核心原则是:先恢复服务,再收集证据,最后分析根因。连接故障、慢查询、死锁、主从延迟、磁盘写满、实例崩溃这六类故障,覆盖了企业 MySQL 运维中 90% 以上的场景。每一类都有明确的排查入口和处理手段。

长期来看,建立监控告警、定期巡检、备份演练和变更管理机制,比掌握多少调优技巧更重要。因为数据库稳定运行的关键不是不出问题,而是出了问题能快速定位、快速恢复,并且同类问题不再重复发生。