mysql中的行级锁死锁与如何避免

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

行级锁死锁是怎么发生的?

MySQL 的行级锁死锁,本质是两个或多个事务互相持有对方需要的锁、又在等对方释放,形成循环等待。InnoDB 会主动检测并回滚代价最小的那个事务,报错

Deadlock found when trying to get lock
—— 这不是异常崩溃,而是引擎的正常干预机制。

关键点在于:行级锁 ≠ 安全锁。它只在**走索引**时生效;一旦查询没命中索引,InnoDB 会退化为锁整张表(或大量无关行),大幅增加冲突概率。

事务 A 执行
UPDATE users SET status=1 WHERE id=100
id
是主键)→ 锁住第 100 行
事务 B 同时执行
UPDATE users SET status=2 WHERE name='alice'
name
无索引)→ InnoDB 扫全表,锁住所有行(包括第 100 行)
此时 A 等 B 释放第 100 行,B 等 A 提交释放全表锁 → 死锁触发

为什么加了索引还死锁?间隙锁(Gap Lock)和 Next-Key Lock 是隐形推手

在默认隔离级别

REPEATABLE READ
下,InnoDB 不仅锁匹配的行,还会锁住「索引间隙」——防止幻读。这意味着即使你查的是唯一值,也可能锁住前后一段范围。

例如表

t
有索引
idx_age
,当前数据中
age
值为 20、25、30:

UPDATE t SET name='x' WHERE age BETWEEN 22 AND 28;

这条语句会锁住

age
在 (20,25) 和 (25,30) 两个间隙,以及 25 这一行(Next-Key Lock)。如果另一个事务正尝试插入
age=23
或更新
age=25
,就可能卡住甚至死锁。

联合索引下更危险:
WHERE a=1
(只用左前缀)仍会加 Gap Lock,哪怕
a
是唯一字段
SELECT ... FOR UPDATE
UPDATE
在范围条件、
LIKE 'abc%'
BETWEEN
场景下极易触发间隙锁竞争
唯一索引等值查询(如
WHERE id=123
)通常只锁单行,不锁间隙 —— 这是少数“安全”场景

避免死锁的四条实操铁律

死锁无法 100% 消除,但可压缩到业务可接受水平。重点不在“检测”,而在“预防设计”。

所有写操作必须走索引:用
EXPLAIN
验证
type
字段不是
ALL
index
;缺失索引的
WHERE
条件,宁可加索引,也不接受全表扫描
统一访问顺序:多个事务更新多行时,强制按主键/索引升序处理。例如批量扣库存,先
ORDER BY sku_id ASC
再遍历更新,避免 A 更新 (100,200),B 更新 (200,100)
事务粒度要小:把“查 → 改 → 发消息 → 记日志”拆成多个短事务;尤其避免在事务里调外部 HTTP 接口或 sleep 读写分离 + 隔离级别降级:非强一致性场景,将隔离级别设为
READ COMMITTED
(关闭间隙锁),配合应用层做重试逻辑

排查死锁:别只看报错,要看
SHOW ENGINE INNODB STATUS

MySQL 每次死锁后,都会把最近一次死锁详情写入引擎状态。这不是日志文件,而是内存快照,需手动抓取:

SHOW ENGINE INNODB STATUS\G

重点关注

LATEST DETECTED DEADLOCK
区块,它会明确列出:

哪个事务持有哪些锁(
HELD LOCKS
哪个事务在等哪一行(
WAITING FOR THIS LOCK TO BE GRANTED
涉及的 SQL、表、索引名、事务 ID、线程 ID

注意:该命令输出只保留最后一次死锁,且不记录历史。生产环境建议搭配监控脚本定期采集,否则问题复现后就再也看不到上下文了。

最常被忽略的一点:死锁往往不是孤立事件,而是高并发下某个慢查询或缺失索引被反复触发的结果。盯着那条报错 SQL 改,不如顺着

SHOW PROFILE
或慢日志,找到它背后真正拖慢事务的元凶。

相关推荐