MySQL 假死故障深度剖析
线上数据库的 “假死” 现象往往隐蔽且影响重大,一次看似突发的连接池耗尽背后,可能隐藏着数据库元数据锁(MDL)与慢查询的复杂纠缠。本文结合真实案例,还原 MySQL 假死故障的排查过程,解析 MDL 锁机制的底层逻辑,并提供可落地的预防方案。
一、故障现象:连接池耗尽引发的系统崩溃
1. 业务告警与日志特征
凌晨 1 点,业务反馈 APP 列表功能完全不可用,后台日志集中抛出连接超时错误:
Caused by: java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 10000ms.
日志显示,数据库连接池(HikariCP)已无可用连接,所有新请求均在等待 10 秒后超时。此时距离最近一次服务发版已过去 7 天,排除了代码变更导致的即时问题。
2. 初步异常表现
- 连接池状态:工单服务连接池完全打满,重启服务后短时间内再次耗尽,排除服务自身 bug。
- 数据库负载:CPU、IO 使用率均处于正常范围(CPU < 30%,磁盘 IOPS < 500),无明显资源瓶颈。
- 网络排查:工单服务所在主机网络流量突发下跌,迁移服务至其他主机后问题依旧,排除网络连通性故障。
所有常规排查方向陷入僵局,直到 DBA 提供的慢查询截图揭示了关键线索:3 条报表查询 SQL 已持续执行32515 秒(约 9 小时),同时存在一个备份进程处于 “
waiting for table flush” 状态。二、根因溯源:MDL 锁争用的连锁反应
1. MDL 锁机制的核心逻辑
MySQL 的元数据锁(MDL)是 server 层的表级锁,用于保证表结构变更与数据操作的一致性:
- 读锁(SHARED):DML 操作(SELECT、INSERT 等)自动申请,多个读锁可共存。
- 写锁(EXCLUSIVE):DDL 操作(ALTER、DROP 等)及部分管理操作(如备份的
FLUSH TABLES)需申请,与读锁互斥。 - 优先级规则:写锁请求进入队列后,会阻塞后续所有读锁请求(即使读锁申请在前),形成 “写锁优先” 的阻塞链。
2. 故障链的形成过程
本次故障中,慢 SQL 与备份任务的交互触发了致命的连锁反应:
- 慢 SQL 持有 MDL 读锁:下午执行的 3 条报表查询(无索引、全表扫描)长时间运行,持续持有
workorder表的 MDL 读锁。 - 备份任务申请 MDL 写锁:凌晨启动的
innobackupex备份需执行FLUSH TABLES,向workorder表申请 MDL 写锁。由于慢 SQL 未释放读锁,写锁进入等待队列。 - 阻塞链扩散:根据 MDL 锁优先级规则,后续所有访问
workorder表的 DML 操作(如 APP 的列表查询)需申请读锁,但被队列中等待的写锁阻塞,导致连接长期挂起。 - 连接池耗尽:挂起的连接占用连接池资源,新请求不断涌入,最终连接池打满,系统完全不可用。
三、应急处理:斩断阻塞源恢复服务
明确根因后,应急处理步骤直击核心阻塞点:
- 终止慢 SQL:通过
kill 599865; kill 599911; kill 599917;终止 3 条长期运行的报表查询,释放 MDL 读锁。 - 中断异常备份:杀死处于 “
waiting for table flush” 的备份进程,清除等待队列中的写锁请求。 - 验证恢复:观察连接池状态,3 分钟内可用连接数回升至正常水平,APP 列表功能恢复响应。
四、预防方案:构建 MDL 锁与慢查询的双重防线
1. 慢 SQL 全生命周期治理
- 执行限制:线上环境启用
max_execution_time(如设置为 30 秒),自动终止超长时间查询:set global max_execution_time=30000; -- 单位:毫秒 - 索引优化:对报表查询涉及的大表(如
workorder)添加联合索引,避免全表扫描(案例中 SQL 未使用索引,扫描行数超 1000 万)。 - 路由隔离:报表查询、数据分析等非核心业务强制路由至只读副本,与主库业务隔离。
2. MDL 锁监控与告警
- 实时监控:通过
performance_schema监控 MDL 锁等待:select * from performance_schema.metadata_locks where locked_table='`workorder`' and lock_type='EXCLUSIVE'; - 告警阈值:当 MDL 锁等待超过 5 秒或等待连接数 > 10 时,触发短信 / 邮件告警,及时介入。
3. 备份策略优化
- 时间窗口:避开业务高峰(如凌晨 1-6 点为业务低峰)执行全量备份,减少与在线业务的资源竞争。
- 低影响备份:使用
--single-transaction参数(InnoDB),避免FLUSH TABLES操作:innobackupex --defaults-file=/etc/my.cnf --single-transaction ...
4. 连接池与事务规范
- 连接池配置:设置合理的超时参数,避免连接长期挂起:
hikari.maximum-pool-size=20 hikari.connection-timeout=30000 hikari.idle-timeout=600000 - 长事务管控:禁止线上存在超过 5 分钟的事务,通过
show processlist定期排查Time字段异常的连接。
五、总结:从故障到体系化防御
本次假死故障的本质,是 “低风险操作” 的叠加效应:单个慢 SQL 不足以压垮数据库,单独的备份任务也不会引发阻塞,但两者在 MDL 锁机制下形成的 “读锁持有 + 写锁等待 + 请求排队” 闭环,最终导致系统雪崩。
对于数据库运维而言,需建立 “现象 - 机制 - 预防” 的完整认知:看到连接池耗尽时,不仅要检查表面的资源指标,更要深入
performance_schema排查锁等待;解决问题后,需从 SQL 规范、监控告警、流程隔离等维度构建防线,让偶发故障转化为系统韧性的提升。
浙公网安备 33010602011771号