首先问题始于生产环境的一批报错
### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
### The error may involve com.pdd.service.quebec.repository.repository.mapper.store.MerchantLogMapper.insertSelective-Inline
### The error occurred while setting parameters
从上面的异常信息可以看到是:merchant_log insert操作在事务回滚的时候,由于在尝试获取锁的时候发生了死锁,导致了异常。
一个经典的insert死锁问题
查了资料发现这个问题是一个经典的死锁问题,是一个因为唯一键冲突导致的死锁场景。
这个死锁产生的逻辑如下:
1. 在 T1 时刻,启动 session A,并执行 insert 语句,此时在索引 c 的 c=5 上加了排他记录锁。
2. 在 T2 时刻,session B 和 session C 要执行相同的 insert 语句,请求排他插入意向锁;但由于发生重复唯一键冲突,各自请求的排他记录锁转成共享记录锁。session B 和 session C 都在索引 c 上,c=5 这一个记录上加了读锁。
3. 在 T3 时刻,session A 回滚,释放索引c=5上的排他记录锁。这时候,session B 和 session C 都试图继续执行插入操作,都要请求索引c=5上的排他记录锁。由于X锁与S锁互斥,T2和T3都等待对方释放S锁。于是,死锁便产生了。
如果此场景下,只有两个事务T1与T2或者T1与T3,则不会引发如上死锁情况产生。
/---------------------------------------------------------------------------------------------------------/
InnoDB的锁类型
1 基本锁: 共享锁(Shared Locks:S锁)与排他锁(Exclusive Locks:X锁)
mysql允许拿到S锁的事务读一行,允许拿到X锁的事务更新或删除一行。
加了S锁的记录,允许其他事务再加S锁,不允许其他事务再加X锁;
加了X锁的记录,不允许其他事务再加S锁或者X锁。
2. 意向锁(Intention Locks)
InnoDB为了支持多粒度(表锁与行锁)的锁并存,引入意向锁。
意向锁是表级锁,可分为意向共享锁(IS锁)和意向排他锁(IX锁)。
事务在请求S锁和X锁前,需要先获得对应的IS、IX锁。
意向锁产生的主要目的是为了处理行锁和表锁之间的冲突,用于表明“某个事务正在某一行上持有了锁,或者准备去持有锁”。
3. 行锁
记录锁(Record Locks) 仅仅锁住索引记录的一行。
间隙锁(Gap Locks) 在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。
Next-Key Locks record lock + gap lock, 左开右闭区间。
插入意向锁(Insert Intention Locks)
Gap Lock中存在一种插入意向锁(Insert Intention Lock),在insert操作时产生。在多事务同时写入不同数据至同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等待。
...
/---------------------------------------------------------------------------------------------------------/