什么是间隙锁

间隙锁(Gap Lock):间隙锁是(RR级别下)一个在索引记录之间的间隙上的锁,可以是两个索引记录之间,也可能是第一个索引记录之前或最后一个索引之后的空间。间隙锁(Gap Lock)是Innodb在可重复读提交下为了解决幻读问题时引入的锁机制
当我们用范围条件而不是相等条件索引数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”。InnoDB也会对这个“间隙”加锁,它锁住的是多行,是一个数据范围,这种锁机制就是所谓的间隙锁(Next-Key锁)。

间隙锁的危害

因为Query执行过程中通过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,也造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。

间隙锁与死锁

最近用户反馈说系统老是出现insert时,等待超时了,最后发现是insert间隙锁!间隙锁是innodb中行锁的一种, 但是这种锁锁住的却不止一行数据,他锁住的是多行,是一个数据范围。间隙锁的主要作用是为了防止出现幻读,但是它会把锁定范围扩大,

有时候也会给我们带来麻烦,我们就遇到了。 在数据库参数中, 控制间隙锁的参数是:innodb_locks_unsafe_for_binlog

这个参数默认值是OFF, 也就是启用间隙锁, 他是一个bool值, 当值为true时表示disable间隙锁。

那为了防止间隙锁是不是直接将innodb_locaks_unsafe_for_binlog设置为true就可以了呢? 不一定!

而且这个参数会影响到主从复制及灾难恢复, 这个方法还尚待商量。

很容易产生间隙锁的位置

间隙锁的出现主要集中在同一个事务中先delete后 insert的情况下, 当我们通过一个参数去删除一条记录的时候,

如果参数在数据库中存在,那么这个时候产生的是普通行锁,锁住这个记录, 然后删除, 然后释放锁。
如果这条记录不存在,就有问题了, 数据库会扫描索引,发现这个记录不存在, 这个时候的delete语句获取到的就是一个间隙锁,然后数据库会向左扫描扫到第一个比给定参数小的值,向右扫描扫描到第一个比给定参数大的值, 然后以此为界,构建一个区间, 锁住整个区间内的数据, 一个特别容易出现死锁的间隙锁诞生了。间隙锁加锁是非互斥的,AB都删除同一个不同在的数据,产生了两个一样的互斥锁,然后都去插入数据,这时候他们都只能拿到自己的间隙锁,拿不到另外一个间隙锁,就进入了互相等待的死锁环节。

间隙锁产生的原因或者说作用

间隙锁的出现是为了在innodb的可重复读隔离级别下,解决幻读问题产生的。

间隙锁会封锁该条记录相邻两个键之间的空白区域,防止其它事务在这个区域内插入、修改、删除数据;所谓间隙是将数据分为不同区间,对该区间范围进行加锁,区间的规则为左开右闭,比如当数据为1,3,5时,对应的区间为(-∞,1],(1,3],(3,5],(5,+∞];

他这个行锁+间隙锁就组成了next—key lock,innodb在可重复度隔离级别下,采用next-key lock来防止幻读,因此实现了最高的隔离级别。

可能产生间隙锁的位置

对于操作的数据是主键索引和普通索引,有不同的加锁规则,如下:

1)、唯一索引只有锁住多条记录或者一条不存在的记录的时候,才会产生间隙锁,指定给某条存在的记录加锁的时候,只会加记录锁,不会产生间隙锁;
2)、普通索引不管是锁住单条,还是多条记录,都会产生间隙锁;

解决方案

①降低事务级别至读已提交(不推荐)

②先判断有无数据,有数据删,无数据则不删,因为删除不存在的数据一定会加间隙锁

③间隙锁非互斥锁,只针对写锁,只要两个线程锁定的区间有交叉就会出现死锁。可以根据条件查出所有主键,根据主键删除数据,这样只会加行锁而不是间隙锁(推荐)

④你如果把隔离级别设置为读提交的话,就没有间隙锁了。但同时,你要解决可能出现的数据和日志不一致问题,需要把 binlog 格式设置为 row。如果读提交隔离级别够用,也就是说,业务不需要可重复读的保证,这样考虑到读提交下操作数据的锁范围更小(没有间隙锁),这个选择是合理的。