必须查看latest detected deadlock区块中每个事务末尾的sql语句(如update...where...),它虽常被截断或参数化,但关键字、表名、字段名仍可定位;再结合holds/waiting锁信息及索引名交叉验证。

怎么看死锁日志里哪个UPDATE卡住了
MySQL 的死锁日志(SHOW ENGINE INNODB STATUS 输出的 LATEST DETECTED DEADLOCK 部分)不会直接写 “UPDATE xxx WHERE id = 123”,而是用事务 ID、锁模式、表名、索引名和主键值(或间隙值)描述冲突。关键要定位到具体 SQL,得结合三块信息:事务持有的锁、等待的锁、以及事务最后执行的语句(*** (1) TRANSACTION: 下的 mysql tables in use 和 Trx has been waiting 前后几行)。
实操建议:
- 先复制整个
LATEST DETECTED DEADLOCK区块,别只截图前两行 - 找
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:和*** (2) HOLDS THE LOCK(S):对应的RECORD LOCKS space id,反查是哪张表哪条记录——用SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE SPACE = <code>xxx查表名,再用SELECT * FROM INFORMATION_SCHEMA.INNODB_SYS_INDEXES WHERE TABLE_ID = <code>yyy查索引 - 事务末尾通常有类似
mysql tables in use 1, locked 1+LOCK WAIT 3 lock struct(s),紧接着就是它最后执行的语句(有时被截断,但关键字如UPDATE、WHERE、字段名往往还在)
为什么大事务UPDATE更容易触发死锁
不是 UPDATE 本身危险,而是大事务拉长了锁持有时间,并扩大了锁覆盖范围。InnoDB 在 UPDATE 时会先加意向锁,再根据 WHERE 条件加行级记录锁(或间隙锁、临键锁)。如果 WHERE 没走索引、或走了范围索引(比如 WHERE created_at > '2024-01-01'),就会锁住大量记录甚至整个索引段。
常见诱因:
-
UPDATE语句没走索引,导致全表扫描 + 全表加锁(即使只改 1 行,也可能锁住所有行) - 事务中混用
SELECT ... FOR UPDATE和UPDATE,且顺序不一致(A 先锁 a 再锁 b,B 先锁 b 再锁 a) - 批量 UPDATE 分批太粗(比如一次更新 10 万行),锁持续几十秒,极大提高碰撞概率
- 应用层重试逻辑没设上限,失败后反复提交相同 UPDATE,让锁冲突雪球式放大
如何快速确认是不是某个UPDATE语句在捣鬼
别等死锁发生再查,主动监控更有效。重点看 INFORMATION_SCHEMA.INNODB_TRX 和 INFORMATION_SCHEMA.INNODB_LOCK_WAITS 这两张表,它们能实时反映“谁在锁谁”。
执行这个查询能揪出长时间运行的 UPDATE:
SELECT trx_id, trx_mysql_thread_id, trx_query, trx_started,
TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS duration_sec
FROM INFORMATION_SCHEMA.INNODB_TRX
WHERE trx_state = 'RUNNING'
AND trx_query LIKE 'UPDATE %'
AND TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 5;
如果发现某条 trx_query 明显卡住(duration_sec 持续上涨),立刻用 KILL <code>trx_mysql_thread_id 中止它,并检查该 SQL 的执行计划:EXPLAIN FORMAT=TRADITIONAL <code>your_update_sql —— 特别注意 type 是否为 ALL 或 index,key 是否为 NULL。
UPDATE死锁日志里出现“lock_mode X locks gap before rec”意味着什么
这表示事务加的是**间隙锁(gap lock)**,不是锁某一行,而是锁住索引中两个值之间的空隙。典型场景是 WHERE 条件用了范围查询但没命中任何现有记录,比如 UPDATE orders SET status = 'done' WHERE user_id = 123 AND created_at > '2099-01-01' —— 此时 InnoDB 会锁住 user_id = 123 索引下所有大于该时间的间隙,防止其他事务插入新记录破坏一致性。
问题在于:间隙锁不可见、不记录在 INNODB_TRX 的 trx_query 里,只出现在死锁日志的 RECORD LOCKS 描述中。排查时容易误判为“没锁啥”,实际它正拦着别人插入。
应对方法:
- 确认业务是否真需要可重复读(RR)隔离级别;若允许读已提交(RC),则间隙锁基本消失(仅外键和唯一索引检查保留)
- 给范围条件字段建联合索引,缩小间隙范围(例如
(user_id, created_at)) - 避免在 UPDATE 中使用无实际匹配的超大范围条件,宁可拆成小范围多次执行
死锁日志里真正难啃的是那些没显式写出 WHERE 值、又带间隙锁的 UPDATE —— 它们不报错、不阻塞自己,却悄悄把其他 INSERT/UPDATE 卡在门口,而且日志里只显示“gap before rec”,不告诉你具体卡在哪两个值之间。










