间隙锁用来解决什么问题?
什么是幻读?
幻读指的是同一个事务前后两次查询同一个范围的时候,后一次查询看到了前一次查询没看到的行。
说明:
- 幻读仅专指“新插入的行”
- 在RR隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的,因此,幻读在“当前读”下才会出现
幻读导致的问题
例:``sql CREATE TABLEt(idint(11) NOT NULL,cint(11) DEFAULT NULL,dint(11) DEFAULT NULL, PRIMARY KEY (id), KEYc(c`) ) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
| sessionA | sessionB | sessionC || --- | --- | --- || begin;<br />select * from t where d=5 for update;<br />update t set d=100 where d=5; | | || | update t set d=5 where id=10;(由于sessionA获取的是id=5这一行的行锁,因此更新id=10这一行记录不会block) | || | | insert into t values(6,6,5);(行锁对更新没用) || commit; | | |由于update是当前读,在commit的时候会将update语句写入binlog,那binlog中记录的内容就是```sqlupdate t set d=5 where id=10;//sessionBinsert into t values(6,6,5);//sessionCupdate t set d=100 where d=5;//sessionA
那在从库重放binlog的时候就会将id=10、id=6这两行记录的d字段更新为100,而主库中的记录id=10的d字段为5,id=6的d字段也为5,出现了主从数据不一致的情况。
为了解决幻读的问题,因此InnoDB引入的间隙锁,产生幻读的原因是行锁只能锁住行,而对于要操作记录间隙的操作(insert就是),InnoDB就只好引入间隙锁来解决。
比如上边的例子,就会加7个间隙锁:(-∞ ,0)、(0,5)、(5,10)、(10,15)、(15,20)、(20,25)、(25,+supremum)。InnoDB 给每个索引加了一个不存在的最大值 supremum。
间隙锁之间不冲突。
next-key lock
间隙锁和行锁合称next-key lock,每个nextkey lock都是前开后闭区间。
间隙锁加锁规则
- 对主键或唯一索引,如果当前读时,where条件全部精确命中(=或者in),这种场景本身就不会出现幻读,所以只会加行记录锁。
- 非索引的列,当前读操作时,会加全表gap锁,生产环境要注意。
- 非唯一索引列,如果where条件部分命中(>、<、like等)或者全未命中,则会加附近Gap间隙锁。例如,某表数据如下,非唯一索引2,6,9,9,11,15。如下语句要操作非唯一索引列9的数据,gap锁将会锁定的列是(6,11],该区间内无法插入数据。
间隙锁和next-key lock带来的问题
导致锁范围变大,可能导致死锁。
问题复现:任意锁住一行,如果这一行不存在,就插入,如果存在就更新,当出现并发时,就会发生死锁。
| sessionA | sessionB |
|---|---|
| begin; select * from t where id=9 for update;(id=9这行记录不存在,加上间隙锁(5,10]) |
|
| begin; select * from t where id=9 for update ;(id=9这行记录不存在,加上间隙锁(5,10]) |
|
| insert into t values(9,9,9);(blocked)(被sessionA的间隙锁阻塞) | |
| insert into t values(9,9,9);(blocked)(被sessionB的间隙锁阻塞) |
上面这种情况就发生死锁了,说明间隙锁的引入可能会导致同样的语句锁住更大的范围,影响了并发度。
如何避免间隙锁带来的问题
不使用可重复度隔离级别,但是为了解决可能出现的数据和日志的不一致情况,需要把binlog的格式改为row。
