在生产环境中,锁等待本身不是问题,但失控的锁等待必然导致事故。
你是否遇到过以下场景?
- 应用QPS正常,但接口响应时间突然全部变慢。
- 服务器CPU、IO利用率不高,但数据库连接线程持续堆积。
- 应用日志开始大量报错:
Lock wait timeout exceeded。
问题的关键不在于“有没有锁等待”,而在于:能否在业务雪崩前,第一时间发现并介入处理?
一、为什么必须监控MySQL锁等待?
核心结论是:锁等待本身不等于异常,但“持续累积”的锁等待一定是系统异常的明确信号。
失控的锁等待通常会引发一系列连锁反应:
- 应用服务器线程池被打满
- 数据库连接数耗尽
- 请求出现雪崩效应
- 大面积业务请求超时
而这些现象,在传统的CPU、IO、磁盘空间监控指标上往往表现为“正常”,从而形成监控盲区。可以说,不监控锁等待,就等于无法洞察数据库真正的性能瓶颈。
二、核心监控指标(聚焦这5项)
有效的监控始于清晰的指标。对于数据库/中间件的锁等待,应重点关注以下五项。
1. 当前锁等待数量(核心指标)
SELECT COUNT(*) FROM performance_schema.data_lock_waits;
告警阈值建议:
> 0:提示(Info级),用于观察
>= 5:警告(Warning级)
>= 10:严重告警(Critical级)
2. 单次锁等待持续时间(判断是否“卡死”)
SELECT
waiting_thread_id,
blocking_thread_id,
wait_time / 1000000000 AS wait_seconds
FROM performance_schema.data_lock_waits;
告警阈值建议:
- 等待时间 > 3 秒:Warning
- 等待时间 > 10 秒:Critical
3. 阻塞事务的运行时长(识别长事务)
SELECT
trx_id,
TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS trx_seconds
FROM information_schema.innodb_trx;
告警阈值建议:
- 事务持续时间 > 30秒:Warning
- 事务持续时间 > 60秒:Critical
4. 锁等待超时次数(趋势指标)
SHOW GLOBAL STATUS LIKE 'Innodb_row_lock_timeouts';
这是一个累计值,监控重点在于其增长速率。该数值的突然陡增,通常意味着系统已处于不稳定状态。
5. 活跃事务数(判断是否堆积)
SELECT COUNT(*) FROM information_schema.innodb_trx;
活跃事务数过多,几乎必然伴随激烈的锁竞争。
三、实时排查SQL(一键定位阻塞链)
当告警触发时,你需要一条能快速厘清“谁在等谁,执行的什么SQL”的语句。以下是线上排障的利器:
SELECT
r.trx_id AS waiting_trx,
r.trx_mysql_thread_id AS waiting_thread,
r.trx_query AS waiting_sql,
b.trx_id AS blocking_trx,
b.trx_mysql_thread_id AS blocking_thread,
b.trx_query AS blocking_sql,
TIMESTAMPDIFF(SECOND, b.trx_started, NOW()) AS blocking_seconds
FROM performance_schema.data_lock_waits w
JOIN information_schema.innodb_trx r ON w.requesting_engine_transaction_id = r.trx_id
JOIN information_schema.innodb_trx b ON w.blocking_engine_transaction_id = b.trx_id;
执行此SQL,可以清晰看到阻塞关系链、涉及的事务线程ID以及正在执行的具体SQL语句,为后续处理提供直接依据。
四、告警策略设计(避免告警疲劳)
错误的告警策略会导致告警被忽视。以下是分级告警的设计建议:
| 告警等级 |
触发条件 |
处理行动 |
| INFO |
锁等待数量 > 0 |
记录日志,持续观察 |
| WARNING |
锁等待数 ≥ 5 或 单次等待 > 3s |
通知值班人员查看 |
| CRITICAL |
单次等待 ≥ 10s 或 出现锁超时错误 |
立即人工介入处理 |
五、监控方案落地(三种主流选择)
方案一:Prometheus + 自定义采集(推荐)
这是最灵活、可控的方案,适合对监控有精细要求的团队。
- 定期采集:通过脚本或Agent,每5-10秒执行一次采集SQL(如上述指标SQL)。
- 指标转换:将查询结果转换为Prometheus格式的指标,例如:
mysql_lock_waits_total
mysql_long_trx_seconds
mysql_lock_wait_seconds
- 告警与可视化:在云原生/IaaS生态的Grafana中配置仪表盘和告警规则。
方案二:使用现成Exporter(快速简单)
例如使用 mysqld_exporter。
- 优点:部署简单,能快速获得大量基础监控指标。
- 缺点:对锁等待的监控粒度较粗,通常无法直接获取阻塞SQL,根因定位困难。适合监控要求不高的中小型团队。
方案三:应用层兜底监控(必备补充)
在业务代码中增加对数据库操作的健康检查:
- 监控SQL执行时间
- 捕获数据库操作超时异常
- 记录失败重试次数
当应用层发现大量超时,可以反向推导出数据库可能存在锁竞争问题。
六、告警触发后的应急处理流程
- 首先,查看阻塞事务:使用第三节的排查SQL或
SELECT * FROM information_schema.innodb_trx ORDER BY trx_started;,找到最老或持锁最多的阻塞事务。
- 谨慎执行Kill操作:大多数情况下,终止阻塞事务能立即恢复。
KILL <blocking_thread_id>;
注意:仅Kill未提交的事务是相对安全的。
- 立即根因排查:问题缓解后,必须排查根本原因,重点检查:
- 索引:UPDATE/DELETE的WHERE条件是否未走索引?
- SQL模式:是否存在大范围更新?
- 事务设计:事务中是否包含了耗时业务逻辑?
- 隔离级别:是否因使用RR(可重复读)级别导致了过多的Next-Key Lock?
七、从根源减少锁等待的6个最佳实践
遵循以下原则,可以从源头大幅降低锁冲突概率:
- 索引先行:确保所有UPDATE/DELETE语句的WHERE条件都有效使用索引。
- 事务精简:事务内只进行数据库操作,避免包含业务逻辑、RPC调用等耗时操作。
- 禁止无索引更新:严格杜绝针对大表执行无WHERE条件或无法使用索引的更新。
- 统一访问顺序:在业务代码中,对不同资源的访问(如多个表、多行数据)尽量保持一致的顺序,避免循环等待。
- 慎用范围查询:在可能的情况下,优先使用等值查询替代范围查询。
- 合理设置隔离级别:除非有强一致性要求,否则考虑使用READ COMMITTED隔离级别,以减少锁的持有范围和时间。
总结
有效的MySQL锁等待监控体系应基于performance_schema和information_schema,核心关注当前等待数量、等待时间、长事务及超时趋势。告警需要科学分级以提升有效性。出现告警后,应优先定位并安全终止阻塞事务以快速恢复业务,继而从索引、事务设计、隔离级别等维度进行根治。结合运维/DevOps工具链(如Prometheus)构建自动化监控,是保障数据库稳定性的关键环节。