数据库突然“卡死“?MySQL 锁等待 3 步定位到根治的实战路径
数据库突然卡死MySQL 锁等待 3 步定位到根治的实战路径【免费下载链接】CS-Base图解计算机网络、操作系统、计算机组成、数据库共 1000 张图 50 万字破除晦涩难懂的计算机基础知识让天下没有难懂的八股文 在线阅读https://xiaolincoding.com项目地址: https://gitcode.com/GitHub_Trending/cs/CS-Base凌晨两点的订单高峰接口大面积超时你连上数据库敲下 SHOW PROCESSLIST发现几十个会话全卡在 Waiting for row lock。这就是典型的 MySQL 锁等待前面的事务握着锁不放后面的人只能排队整条业务链路被拖死。下面按排查顺序走一遍先定性再看 InnoDB 的排队规则然后三步锁定元凶最后止血加除根。先判断它是不是真的在锁等待别急着拉一堆命令先用 30 秒确认症状对得上。一条平时只要几毫秒的简单 SQL突然变成十几秒而执行计划没变、索引也在走——大概率不是慢 SQL是在等锁。你敲 SHOW PROCESSLIST看到一大片会话长时间挂在 Waiting for row lock 上Time 还在持续上涨——是行锁在排队。状态里出现 Waiting for table metadata lock——说明有人在跑 DDL 或有事务没提交这张表等于整体冻结。再看监控CPU 拉高但 QPS 反而掉——线程没在干活全在干等这也是锁等待的典型体征。InnoDB 的排队规则占座、锁区间、连座带区间把 InnoDB 的行级锁想象成剧院占座就很直白。记录锁是占座我占了 1007 号座别人坐不了但别的座不受影响。间隙锁是锁区间我不关心哪个具体的座只是宣布 1006 到 1008 之间的过道归我我的事务结束前谁也不许进来插队——这是防幻读的核心手段。Next-Key 锁就是连座带区间一起锁既占住 1007 这个座又锁住它前面的过道。默认的可重复读RR级别下InnoDB 就是按 next-key 锁这个标准动作来加锁的。所以两个事务各锁了一段重叠的区间再互相等对方松手你看到的死锁就是这么来的。排查动线顺着三步走第 1 步一条命令看清谁在等谁-- 把当前持有的、以及正在请求的行锁全部列出来 SELECT * FROM performance_schema.data_locks\G; 重点盯三个字段LOCK_TYPE 为 RECORD 表示行级锁LOCK_MODE 最讲究——X, REC_NOT_GAP是记录锁X, GAP是间隙锁只有光秃秃的X才是 next-key 锁LOCK_DATA 是锁住区间的右边界。看到两个事务锁住同一段范围、彼此都处于等待状态问题基本就露出真身了。第 2 步翻死锁日志定位互锁的双方SHOW ENGINE INNODB STATUS\G;在输出里找 LATEST DETECTED DEADLOCK 这一段。要看三样东西参与互锁的两个事务 ID、各自持有了什么锁又在等什么锁、以及各自最后执行的那条 SQL。死锁检测默认开启一旦触发InnoDB 会主动回滚其中一方并把完整现场记在这里这是你还原事故最权威的证据。第 3 步用 sys 视图锁定元凶事务SELECT * FROM sys.innodb_lock_waits\G;这个视图直接把谁在等谁串成一条线等待方是谁、它在跑什么 SQL阻塞方blocking_trx是谁、它握着什么。再配合看一眼 sys.innodb_trx 里的 trx_started哪个长事务占着锁不放一目了然。先止血再除根止血KILL 掉阻塞事务的正确姿势-- 先从上面的 sys 视图拿到阻塞方的会话 ID再按这个 ID 断开连接 KILL blocking_session_id;注意 KILL 后面跟的是连接 ID不是事务 ID这两者别搞混。如果阻塞方是个不能动的大任务可以临时把等待门槛调低让排队方更快超时报错而不是干熬SET GLOBAL innodb_lock_wait_timeout 10;——默认 50 秒在故障现场太漫长了。除根让锁等待不再复发如果 UPDATE、DELETE 或 SELECT ... FOR UPDATE 的 WHERE 条件走不到索引就会全表扫描一行行把 next-key 锁加满等于把整张表锁住——这种语句先看执行计划确认命中索引才准上生产。如果事务里夹了 HTTP 调用、发消息这类慢动作那锁就一直被握着不放把非数据库操作挪出事务、把大事务拆小。如果多条业务链路按不同顺序抢同一批资源死锁概率很高把访问顺序统一起来比如都先操作主键小的那张表。如果你之前靠 SELECT ... FOR UPDATE 做幂等校验直接把对应列改成唯一索引用唯一性约束兜底连锁都不用握了。如果业务确实用不到可重复读把隔离级别降到 READ COMMITTEDRC 下间隙锁基本消失锁范围会小很多。复盘一张订单表是怎么救回来的那天凌晨一点订单服务开始批量报超时告警。拉 processlist 一看十多个会话全挂在 t_order 的 Waiting for row lock 上。翻死锁日志互锁双方写得明明白白两个事务都先执行 SELECT ... FOR UPDATE 做幂等校验、再插入新订单。当时的 order_no 只是普通索引可重复读下两条等值查询都没查到记录各加了一把范围 (1006, ∞] 的 next-key 锁随后两条 INSERT 都要在这个区间里拿插入意向锁插入意向锁与间隙锁互斥两边就这么对等了 50 秒直到超时报错。上午我们动了三刀把 order_no 升级为唯一索引删掉 SELECT ... FOR UPDATE改靠唯一索引的重复键报错来兜底幂等隔离级别调整为 READ COMMITTED。当天下午观察这张表上的锁等待归零订单处理吞吐量比前一天高出 40%。防锁等待自查清单所有 UPDATE / DELETE / SELECT FOR UPDATE 是否都命中了索引全表扫描加锁是头号坑。sys.innodb_trx 里有没有超过 5 秒的长事务在占座同一批资源的访问顺序在所有业务链路里是否一致幂等校验是不是还靠 SELECT ... FOR UPDATE能否换成唯一索引隔离级别真的需要 RR 吗能降 RC 就降。⚡ 锁等待很少是意外它几乎都对应索引、事务或业务流程里的某个设计选择把这三处理顺processlist 里的排队自然就会消失。想往下深挖可以看仓库里的 MySQL 死锁了怎么办 和 MySQL 是怎么加锁的。【免费下载链接】CS-Base图解计算机网络、操作系统、计算机组成、数据库共 1000 张图 50 万字破除晦涩难懂的计算机基础知识让天下没有难懂的八股文 在线阅读https://xiaolincoding.com项目地址: https://gitcode.com/GitHub_Trending/cs/CS-Base创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考