行级锁死锁是因事务间循环等待索引行锁或间隙锁导致,InnoDB自动检测并回滚代价最小事务;根本原因包括未走索引致表级锁、间隙锁/Next-Key Lock在范围查询中扩大锁定范围,预防需确保索引访问、统一更新顺序、缩小事务粒度、合理降级隔离级别。
MySQL 的行级锁死锁,本质是两个或多个事务互相持有对方需要的锁、又在等对方释放,形成循环等待。InnoDB 会主动检测并回滚代价最小的那个事务,报错 Deadlock found when trying to get lock —— 这不是异常崩溃,而是引擎的正常干预机制。
关键点在于:行级锁 ≠ 安全锁。它只在**走索引**时生效;一旦查询没命中索引,InnoDB 会退化为锁整张表(或大量无关行),大幅增加冲突概率。
UPDATE users SET status=1 WHERE id=100(id 是主键)→ 锁住第 100 行UPDATE users SET status=2 WHERE name='alice'(name 无索引)→ InnoDB 扫全表,锁住所有行(包括第 100 行)在默认隔离级别 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)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 改,不如顺着 SHOW PROFILE 或慢日志,找到它背后真正拖慢事务的元凶。