MySQL 5.7 中 DELETE 使用索引仍发生 InnoDB 死锁,原因是什么?
结论
使用 key_other_table 索引,不能保证 DELETE 不会发生死锁。InnoDB 删除记录时仍要锁定相应的聚簇索引记录,并维护相关索引。执行计划显示使用了索引,也不代表语句只涉及一条记录,或不会与其他事务争用锁。
现有日志不足以确认这两笔事务具体在哪些记录上形成了循环等待。page no 3 表示记录所在的数据页,不代表两笔事务争用的是同一条记录;locks rec but not gap 表示报告的是记录锁,而不是间隙锁。日志片段没有显示记录的 heap no 和对应的物理记录,因此无法据此映射到具体的 key_my_table 或 key_other_table。
为什么不同键值仍需继续排查
EXPLAIN 中的 rows=1 是优化器的估算值,不保证实际匹配行数就是 1。一个 key_other_table 值可能对应多行;DELETE 会检查匹配记录,并在删除过程中维护聚簇索引和二级索引。
如果两条语句确实只删除各自键值对应的行,且两组行完全不重叠,那么不同键值本身无法解释它们为何会争用同一条聚簇索引记录。应进一步检查完整死锁图中的锁记录、两笔事务各自已持有的锁,以及事务期间是否还执行了其他语句。死锁日志说明检测到了循环等待,但仅凭查询参数无法判断循环是如何形成的。
排查步骤
- 获取完整的死锁报告,重点查看双方
HOLDS THE LOCK(S)和WAITING FOR THIS LOCK TO BE GRANTED下的heap no、物理记录内容、索引名和锁模式。将记录主键与实际数据对应起来,确认双方是否锁定了重叠记录。 - 检查应用中的完整事务边界,确认两笔事务是否只执行了这条 DELETE,以及是否在同一事务中先后访问了其他表或索引。不同语句以不同顺序获取锁,也可能形成死锁。
- 检查实际数据分布和匹配行数。
EXPLAIN的行数是估算值;在合适的事务隔离级别和并发条件下,对照实际匹配行进行确认,查看一个外键值对应多少行,以及其他谓词筛掉了哪些记录。 - 评估复合索引能否缩小扫描范围。例如,如果工作负载经常按这三个条件删除,可以评估
(key_other_table, some_boolean, some_timestamp)。效果取决于数据分布和其他查询,需要结合执行计划与实际负载判断;添加索引不保证消除死锁。
ON UPDATE CASCADE 描述的是更新被引用键时的级联行为,不能单独解释这两条子表 DELETE 为什么发生死锁。还应检查完整事务是否涉及相关父表操作。
处理建议
InnoDB 并发事务需要考虑死锁情况。应用应能识别死锁导致的事务回滚,并在回滚后以有限次数重试整个事务;重试时可以加入短暂退避,避免多个线程立即同步重试。如果确认多个事务可能以不同顺序访问同一业务资源,再考虑统一访问顺序或缩短事务持锁时间。
当前日志片段缺少定位具体记录所需的信息。应先结合完整死锁报告和事务执行顺序确认冲突,再决定是否调整索引或并发方式。
备注:内容仅供参考。
