死锁报错本身不是sql写错了,而是多个事务在争抢同一组行时加锁顺序不一致;频繁报错说明索引设计或事务逻辑存在系统性缺陷,须从锁粒度、访问顺序和事务生命周期三方面优化。

死锁报错本身不是SQL写错了,而是多个事务在争抢同一组行时加锁顺序不一致;频繁报错(比如每分钟3+次)说明索引设计或事务逻辑存在系统性缺陷,必须从锁粒度、访问顺序和事务生命周期三方面动手。
为什么EXPLAIN显示走了索引,但还是死锁?
索引“走了”不等于“走对了”。常见陷阱是联合索引字段顺序与WHERE条件不匹配,导致只用到前缀,后半段条件退化为内存过滤,InnoDB仍需扫描并加锁大量无关行。
-
WHERE pool_id = ? AND status = ?用了(pool_id, identifier, status)索引 →identifier允许为 NULL 时,status可能无法被索引驱动,实际只锁pool_id对应的整个范围 -
WHERE status = 'pending' AND user_id = 456却只有单列status索引 → 无法命中,全表扫描加锁 -
WHERE created_at > '2026-01-01' AND user_id = 123建了(user_id, created_at)是对的;但若建了(created_at, user_id),范围查询前置会让user_id失效
如何快速验证某条SQL是否真走索引且锁行数合理?
别只信 EXPLAIN 的 key 和 rows,要结合真实锁行为看:
- 执行
SHOW ENGINE INNODB STATUS\G,在LATEST DETECTED DEADLOCK中找该SQL对应的事务块,看lock_mode X locks rec but not gap是否出现——没出现 gap 锁,说明没触发间隙锁扩散 - 查
information_schema.INNODB_TRX中该事务的trx_rows_locked值,如果远高于预期(比如查1行却锁了5000行),基本就是索引没生效或顺序不对 - 开启
innodb_print_all_deadlocks = ON后,在错误日志里搜TABLE `xxx` LOCK WAIT行,确认锁的是具体记录还是区间
事务里UPDATE多行,怎么避免交叉加锁?
核心是让所有事务对同一组行按**完全相同的物理顺序**加锁,最简单可靠的方式是强制按主键升序排列条件值。
- 把
UPDATE t SET x=1 WHERE id IN (5,1,3)改成UPDATE t SET x=1 WHERE id IN (1,3,5)—— InnoDB 按主键顺序加锁,消除反向扫描可能 - 批量更新带排序逻辑时,先
SELECT id FROM t WHERE ... ORDER BY id FOR UPDATE拿到有序ID列表,再拼进后续UPDATE,避免优化器自行决定扫描顺序 - 跨表操作必须固定顺序:比如所有业务逻辑统一先锁
inventory再锁order,绝不能有的先 order 后 inventory
READ COMMITTED能关掉间隙锁,但要注意什么?
从 REPEATABLE READ 切到 READ COMMITTED 能禁用间隙锁,大幅收窄锁范围,但业务层必须接受相应语义变化:
- 幻读不再被阻止:分页查询可能重复或丢失数据,
SELECT ... FOR UPDATE不再阻塞后续插入 -
binlog_format必须为ROW,否则主从复制可能不一致 - 某些依赖快照读的逻辑会失效,比如“读一次A值 → 做判断 → 再读一次A值”,在 RC 下两次读可能看到不同结果
- 确认应用没用
SELECT ... LOCK IN SHARE MODE依赖间隙锁做并发控制,否则逻辑会出错
真正难的不是调参或建索引,而是把“锁顺序”这个隐性契约落到每一行代码里——比如 ORM 自动生成的批量更新语句,是否可控?异步任务和定时脚本是否遵守同一套表访问顺序?这些细节比死锁日志里的 SQL 更容易被忽略。











