MySQL多线程插入含唯一约束的表时触发死锁的原因排查
MySQL多线程插入含唯一约束的表时触发死锁的原因排查
嗨,我来帮你拆解下这个多线程插入触发死锁的问题~
首先,咱们得先搞懂InnoDB在处理带唯一约束的INSERT操作时的锁逻辑:
当你执行INSERT语句插入带唯一约束的字段时,InnoDB会分两步走:
- 先检查这个唯一键值是否已经存在,这个过程中会对唯一索引的对应间隙(或者记录)加一个共享锁(S锁)
- 如果检查发现值不存在,就会把这个共享锁升级为排他锁(X锁),然后完成插入操作
那多线程场景下的死锁是怎么发生的呢?举个具体的例子:
假设有线程A和线程B同时执行插入不同uuid的操作:
- 线程A先给自己要插入的uuid对应的索引间隙加了S锁
- 线程B也给自己要插入的uuid对应的索引间隙加了S锁
- 接下来线程A要把S锁升级成X锁才能插入,但发现线程B持有相关的锁,只能等着
- 线程B同样要升级S锁为X锁,也发现线程A持有锁,也等着
- 这就形成了循环等待,InnoDB检测到后就会触发死锁,抛出1213错误
回到你的代码,每个线程都用独立的数据库连接插入随机uuid,多线程并发执行时,非常容易出现上面这种锁等待的循环,最终触发死锁。
接下来给你几个可行的解决办法:
- 用
INSERT IGNORE替代普通INSERT:这样当唯一键冲突时会直接忽略操作,而且InnoDB在处理INSERT IGNORE时会直接尝试获取排他锁,跳过共享锁的阶段,从根源减少死锁的可能 - 用
ON DUPLICATE KEY UPDATE:哪怕你只是写个空更新(比如ON DUPLICATE KEY UPDATE id=id),也会让InnoDB直接获取排他锁,避免共享锁升级的问题 - 应用层加重试逻辑:捕获到1213死锁错误时,重试插入操作,死锁本身是偶发的,重试大多能成功
- 调整事务隔离级别:把数据库的隔离级别改成
READ COMMITTED,在这个级别下InnoDB对唯一索引的锁会优化成只加记录锁,不会加间隙锁,能大幅降低死锁概率(这个不影响业务的话很推荐)
备注:内容来源于stack exchange,提问作者pin
相关产品推荐
相关产品推荐

