执行show engine innodb statusg,重点查看“(1) transaction”和“(2) transaction”块,分析各自sql、持有锁(holds the lock(s))、等待锁(waiting for this lock to be granted)及锁类型(x/s/gap/insert_intention),结合explain确认是否真正走索引,避免全表扫描或隐式转换导致锁范围扩大。

怎么看最近一次死锁的详细信息
mysql 每次检测到死锁,都会在 SHOW ENGINE INNODB STATUS 的输出中保留**最后一次死锁的完整现场**,这是排查最核心的入口。别依赖错误日志——它通常只记“Deadlock found when trying to get lock”,而真正关键的 SQL、索引、锁类型、哪个事务被回滚,全在 SHOW ENGINE INNODB STATUS 里。
- 执行
show engine innodb statusG(加G是为了格式化可读) - 重点找
*** (1) TRANSACTION:和*** (2) TRANSACTION:块:看各自执行的 SQL、持有的锁(HOLDS THE LOCK(S))、等待的锁(WAITING FOR THIS LOCK TO BE GRANTED) - 注意锁类型缩写:
X是排他锁,S是共享锁,GAP是间隙锁,INSERT_INTENTION是插入意向锁 - 如果看到
index idx_user_id上两个事务互等对方某条记录的X锁,基本就是加锁顺序不一致导致的
为什么加了索引还是死锁?查锁是否真的命中索引
很多同学加了索引就以为万事大吉,但死锁依然频发——根本原因是 SQL **没走预期索引**,退化成全表扫描或范围扫描,锁住大量无关行。InnoDB 的行锁是建立在索引上的,没走索引 = 锁表级行为。
- 用
EXPLAIN看执行计划:确认type是ref/eq_ref(精确匹配),而不是range或更差的ALL - 特别警惕
WHERE条件含函数、隐式类型转换(如user_id = '123'而字段是INT)、OR多条件拼接——这些都可能导致索引失效 - RR 隔离级别下,哪怕走了索引,范围查询(如
id > 100 AND id )也会触发 <code>Next-Key Lock(行锁 + 间隙锁),扩大锁范围;而 RC 级别虽禁用大部分间隙锁,但唯一键冲突检查时仍会用到
怎么快速定位正在堵着的事务和锁
死锁发生后,可能已有事务卡在锁等待中,不及时处理会影响后续请求。别只盯着“最后一次死锁”,要实时看当前谁在等、谁在占。
- 查活跃事务:
select * FROM information_schema.INNODB_TRX,重点关注TRX_STATE(是否LOCK WAIT)、TRX_STARTED(是否长事务)、TRX_QUERY(正在执行的 SQL) - 查锁等待关系:
SELECT * FROM information_schema.INNODB_LOCK_WAITS,它直接关联了阻塞者(BLOCKING_TRX_ID)和被阻塞者(REQUESTING_TRX_ID) - MySQL 8.0+ 可用
performance_schema.data_locks查具体锁在哪张表、哪个索引、哪一行(需提前开启相关 instrument) - 慎用
KILL:只 kill 等待方(TRX_STATE = 'LOCK WAIT'),不要 kill 持有锁的事务,否则可能引发二次死锁或数据不一致
代码层怎么避免重蹈覆辙
dba 能帮你查清原因,但根治得靠业务逻辑改造。死锁不是数据库问题,是并发访问模式暴露了设计缺陷。
- 所有涉及多行更新的场景,强制按主键/唯一键**升序排序后统一加锁**:比如分配资金给多个借款人,先
SELECT id FROM borrower WHERE ... ORDER BY id FOR UPDATE,再逐条更新,确保所有事务锁行顺序一致 - 拆分大事务:避免在事务内做 http 调用、文件读写、sleep 等耗时操作;把“查 → 算 → 改”拆成“查 → 算 → 提交 → 改”,让锁持有时间尽可能短
- 减少显式锁:能用
UPDATE ... WHERE id = ? AND version = ?乐观锁解决的,就别用SELECT ... FOR UPDATE;尤其在高并发计数、库存扣减等场景 - ORM 尤其要注意:mybatis 的
<foreach></foreach>生成 IN 查询时,若传入 ID 列表无序,不同事务锁顺序天然不一致——务必先排序再传参
最容易被忽略的一点:死锁日志里那个被回滚的事务,往往不是“错”的那个,而是 InnoDB 挑的“代价最小”的牺牲者。你看到的错误堆栈,只是结果,不是病因——必须回到 SHOW ENGINE INNODB STATUS 去还原两个事务的真实执行路径。