mysql死锁发生后如何排查_mysql死锁解决方法

来源:这里教程网 时间:2026-02-28 20:53:19 作者:

怎么看最近一次死锁的详细信息

MySQL 每次检测到死锁,都会在

SHOW ENGINE INNODB STATUS
的输出中保留**最后一次死锁的完整现场**,这是排查最核心的入口。别依赖错误日志——它通常只记“Deadlock found when trying to get lock”,而真正关键的 SQL、索引、锁类型、哪个事务被回滚,全在
SHOW ENGINE INNODB STATUS
里。

执行
show engine innodb status\G
(加
\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
去还原两个事务的真实执行路径。

相关推荐