UPDATE语句能否使用插入意向锁?及并发更新死锁问题咨询
答案是肯定的——虽然插入意向锁(Insert Intention Lock)通常被认为是INSERT操作专属的锁类型,但在特定场景下,UPDATE语句也会触发它的使用,你遇到的死锁案例就是典型场景。
先拆解下你提供的死锁细节:
2019-04-18 15:54:09 0x7f85cff7e700
*** (1) 事务:
TRANSACTION 70678199277,ACTIVE 0秒,开始索引读取
mysql 使用的表:1,已锁定:1
LOCK WAIT
137个锁结构,堆大小24784,689个行锁,undo日志条目10
MySQL线程ID 6314744,OS线程句柄140210780473088,查询ID 1764862374
10.32.94.170 m_pr_d090 正在搜索待更新的行UPDATE table1 SET status =1 WHERE c_Id = 24671 and d_Id =1247910
*** (1) 正在等待获取以下锁:
RECORD LOCKS space id 12918 page no 4088 n bits 688
索引 idx_cinemaid_dcardid_status 属于表mr.table1
trx id 70678199277 lock_mode X waiting
*** (2) 事务:
TRANSACTION 70678199289,ACTIVE 0秒,正在更新或删除
mysql 使用的表:1,已锁定:1
144个锁结构,堆大小24784,721个行锁,undo日志条目13
MySQL线程ID 6313652,OS线程句柄140212696508160,查询ID 1764862806
10.4.189.142 m_pr_d090 正在更新UPDATE table1 SET status =1 WHERE c_Id = 24670 and d_Id =1247910
*** (2) 持有以下锁:
RECORD LOCKS space id 12918 page no 4088 n bits 688
索引 idx_cinemaid_dcardid_status 属于表mr.table1
trx id 70678199289 lock_mode X
*** (2) 正在等待获取以下锁:
RECORD LOCKS space id 12918 page no 4088 n bits 688
索引 idx_cinemaid_dcardid_status 属于表mr.table1
trx id 70678199289 lock_mode X locks gap before rec insert intention waiting
为什么UPDATE会触发插入意向锁?
这里的核心原因是你的联合索引idx_cinemaid_dcardid_status包含了status字段——当你执行UPDATE table1 SET status =1时,InnoDB需要先从这个联合索引中删除旧的索引条目(包含原来的status值),然后插入一条新的索引条目(包含更新后的status=1)。这个插入新索引条目的过程,就会触发插入意向锁的申请。
死锁的具体形成过程
结合你的场景,死锁的链条是这样的:
- 事务2先执行UPDATE,成功锁定了
c_Id=24670, d_Id=1247910对应的旧索引记录(持有X锁),接着尝试插入新的索引条目,此时需要申请插入意向锁,但发现事务1已经锁定了该位置附近的间隙或记录,于是事务2进入等待状态。 - 事务1几乎同时执行UPDATE,尝试锁定
c_Id=24671, d_Id=1247910对应的旧索引记录,但这条记录所在的间隙已经被事务2的锁覆盖,导致事务1也进入等待状态。 - 两个事务互相等待对方释放锁,最终形成死锁。
解决这个死锁的建议
- 调整索引结构:如果业务上不需要
status作为联合索引的一部分,把它从idx_cinemaid_dcardid_status中移除。这样UPDATE修改status时就不需要更新索引,也就不会触发插入操作,从根源避免这类死锁。 - 统一操作顺序:让所有事务按照相同的顺序执行UPDATE(比如按照
c_Id从小到大的顺序),避免交叉等待的情况。 - 降低隔离级别:如果业务允许,把事务隔离级别调整为
READ COMMITTED,InnoDB在这个级别下会关闭间隙锁(除了唯一索引和外键场景),能大幅减少死锁概率。
内容的提问来源于stack exchange,提问作者Tao.Aaron

