问题背景
咱们在开启多线程对数据库进行操作的时候,先批量对数据进行删除,而后再新增,原本想着是思考到不走更新,性能会晋升,然而执行的时候发现报错,执行的 sql 期待超时,阻塞了过程,dbcp 连接池被打满,数据库表拜访不可用。针对这个问题,咱们进行了深刻的开掘,逐步解开了问题的假相。
看下具体的业务实现细节
- 表定义
- 当初导⼊⼀批数据 A 的汇合,A 的定义如下所示:
接下来复现问题操作
- 依据 t1 的值查问表 a 中有没有对应的记录
- 如果有值,则更新 t2 的值
- 如果没查到后果,则执行 insert 插入操作
这里批量操作咱们采纳了多线程的形式来执行
问题复现
- step1 – ⾸先插⼊测试数据
- step2 – 咱们开启两个窗⼝去模仿死锁。
Session1:
Session2:
此时,Session 1 和 Session 2 都会对区间 (20, ⽆穷⼤) 加锁, 因为间隙锁只是⽤来防⽌其余事务在区间中插⼊数据。 - step3 – Session1 持续插⼊操作:
此时 Session1 阻塞(因为 Session2 持有间隙锁)。
- step4- 紧接着 Session2 持续插⼊操作:
此时 Session2 死锁,因为 Session1 持有间隙锁。⽽咱们的代码⾥⾯,因为波及到多线程在事务⾥进⾏先删除后插⼊的操作,就会发⽣死锁。
不走更新操作,先删除,后插入,保障只有 2 次数据库操作。
问题起因
查问相干材料得悉,引起死锁的起因是 MYSQL 的间隙锁。
间隙锁
间隙锁(Gap Lock)是 Innodb 在可反复读提交下为了解决幻读问题时引⼊的锁机制,幻读的问题存在是因为新增或者更新操作,这时如果进⾏范畴查问的时候(加锁查问),会呈现不⼀致的问题,这时使⽤不同的⾏锁曾经没有方法满⾜要求,须要对⼀定范畴内的数据进⾏加锁,间隙锁就是解决这类问题的。在可反复读隔离级别下,数据库是通过⾏锁和间隙锁独特组成的(next-key lock)来实现的。
⾏锁和间隙锁的定义如下所示:
- record lock:⾏锁,也就是仅仅锁着独自的⼀⾏。
- gap lock:间隙锁,仅仅锁住⼀个区间(留神这⾥的区间都是开区间,也就是不包含边界值)。
- next-key lock:record lock+gap lock,所以 next-key lock 也就半开半闭区间,且是下界开,上界闭。
加锁规定个性
加锁规定有⼀些个性,其中咱们须要关注的有:
- 加锁的根本单位是(next-key lock), 他是前开后闭准则
- 查找过程中拜访的对象会减少锁
- 间隙锁仅阻⽌其余事务插⼊间隙。在删除数据的时候,会去加间隙锁,然而多个事务是能够同时对⼀个间隙去加锁的,⽽如果须要对该间隙进⾏插⼊,则须要期待锁开释。
解决形式
1、将事务隔离级别将为 read commit.
间隙锁只存在于可反复读的隔离级别下,因为要防⽌幻读。这个⽅法不事实,不可能为了这个问题 把整个线上数据库隔离级别给改掉。
2、防止先删除后插⼊的操作.
批改代码,防止先删除后插⼊的操作。就义性能,在业务中,先依据唯⼀索引查出存在的记录,而后对存在的记录进⾏依据主键 Id 在循环中更新,对于不存在的记录进⾏批量插⼊。