MySQL插入子表时触发死锁(Deadlock)问题排查求助
首先,咱们先拆解下你遇到的这个死锁场景——虽然你说没对同一行做修改,但InnoDB的锁机制在REPEATABLE-READ隔离级别下,有些隐藏的锁行为很容易触发死锁,我给你分析几个最可能的原因和对应的解决办法:
1. 最常见的根源:子表外键字段未建索引
你给出的table_b结构里,REF_ID是关联父表的外键,但没提到它有单独的索引。这是个典型的坑:当你往table_b插入记录时,InnoDB需要验证外键约束(确保父表table_a中存在对应的REF_ID),同时还要维护外键的参照完整性。如果REF_ID没有索引,InnoDB会对整个table_b做全表扫描,这个过程中会给table_b的大量行加锁(甚至等效于表级锁的场景)。当有多个并发事务执行同样的插入操作时,这些锁很容易形成循环等待,触发死锁。
解决办法:给table_b的REF_ID创建索引:
CREATE INDEX idx_table_b_ref_id ON table_b(REF_ID);
这能让InnoDB快速定位到关联的父表行,避免全表扫描加锁,几乎能解决大部分这类场景的死锁。
2. 查看死锁日志精准定位问题
光猜原因不够,咱们可以直接看InnoDB的死锁日志,它会告诉你死锁发生时两个事务各自持有什么锁、在等什么锁。执行这条命令:
SHOW ENGINE INNODB STATUS;
找到LATEST DETECTED DEADLOCK部分,里面会详细列出死锁的事务ID、SQL语句、锁类型和等待的资源。比如如果看到是父表的行锁冲突,那可能是有其他并发事务在操作父表的同一行(哪怕你没意识到);如果是子表的间隙锁冲突,那可能和REPEATABLE-READ的间隙锁机制有关。
3. 调整事务隔离级别(可选)
你的事务隔离级别是REPEATABLE-READ,这是InnoDB的默认级别,但它会使用间隙锁(Gap Lock)来防止幻读,这会增加锁冲突的概率。如果你的业务场景能接受READ COMMITTED隔离级别(比如不需要严格的重复读),可以降低隔离级别:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
这个级别下InnoDB不会使用间隙锁,能减少很多不必要的锁冲突,降低死锁发生的概率。
4. 确保并发事务的操作顺序一致
虽然你已经是先插父表再插子表,但要确保所有并发事务都遵循同样的操作顺序——比如所有涉及table_a和table_b的事务,都先操作table_a,再操作table_b。如果有某个事务反过来(先插子表再插父表),就很容易形成循环等待触发死锁。
按照上面的步骤排查,尤其是先给REF_ID加索引,应该能解决你的问题。如果死锁还存在,把SHOW ENGINE INNODB STATUS的死锁日志贴出来,咱们再进一步分析。
内容的提问来源于stack exchange,提问作者Jaypal Sodha

