MySQL:逃不掉的锁事,间隙锁

我们知道在MySQL中存在幻读的情况,也就是一个事务在读取某个范围内的记录时,发现了另一个事务在该范围内新增了记录(或者删除了记录),导致两次读取的记录数量不一致 , 进而产生了“幻觉”一般的现象 。也就是说 , 幻读是指在多个事务同时读取同一范围内的记录时所产生的矛盾现象 。
MySQL为了解决幻读一般采用快照读和间隙锁的方式,其中快照读在之前的文章已经多次提及,本篇文章重点介绍间隙锁 。
间隙锁意如其名,就是锁定符合条件但是实际不存在的记录,也就是一定的区间 , 防止其他事务在某个事务执行期间向该区间插入新的记录 。
为清楚梳理间隙锁的作用,我们在本文中使用的示例表如下:
CREATE TABLE `t` (`id` int(11) NOT NULL,`c` int(11) DEFAULT NULL,`d` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `c` (`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);在示例表中执行如下语句:
begin;select * from t where d=5 for update;commit;语句中的select for update就是为了在查询时,对相关语句进行加锁,避免其他用户对该表进行插入、修改、删除等操作,造成表的不一致 。
d=5这一行对应主键为Id=5,执行select语句后改行会被加写锁,并在commit后释放 。但是由于d列没有索引 , 所以会被全表扫描,这时候真实的加锁逻辑为:

  1. 全表扫描一般指主键索引树扫描;
  2. 对于会不会被加锁:
RC级别下,只会在满足条件的行加行锁(直至事务commit/rollback才会释放),不满足条件的是先加锁然后再直接释放锁;
RR级别下会加行锁+全表间隙锁(next-key lock是左开右闭 , 间隙锁是左开右开);
这里可以先记住这个逻辑,我们在下面的文章中会逐步开始介绍 。1 幻读1.1 幻读是什么注意,如下的结论都是假设存在,从而引入间隙锁的概念 。
如果没有间隙锁,只有行锁,即:上面的语句只会锁?。篿d=5的这一行数据,那么就会出现如下图所示的场景:
MySQL:逃不掉的锁事,间隙锁

文章插图
for update在当前读可以理解为:MySQL认为for update已经给当前的行加了写锁 , 因此没有必要再进行快照读 , 但是这样会造成幻读的问题 。
如果没有间隙锁,就会出现如下的结果:
  1. Q1 只返回 id=5 这一行;
  2. 在 T2 时刻,session B 把 id=0 这一行的 d 值改成了 5,因此 T3 时刻 Q2 查出来的是 id=0 和 id=5 这两行;
  3. 在 T4 时刻,session C 又插入一行(1,1,5),因此 T5 时刻 Q3 查出来的是 id=0、id=1 和 id=5 的这三行 。
Q3读到id=1这一行的现象就是”幻读“,即:在同一个事务中 , 两次读取到的数据不一致的情况可称为幻读和不可重复读,其中幻读针对insert导致的数据不一致,不可重复读针对的delete/update导致的数据不一致 。注意:这里的读指的是当前读 , 比如查询语句中包含for update、in share mode,以及修改删除语句都会开启当前读,否则就是快照读 。
  • 快照读:指的是在语句执行之前或者在事务开始的时候创建一个一致性视图 , 后面的读都是基于这个视图,不会再去查询最新的值;
  • 当前读:指的是更新之前必须先查询当前的值,因此叫做当前读 , 比如说:select for update或者select in share mode;
SELECT ... LOCK IN SHARE MODE走的是IS锁(意向共享锁) , 即在符合条件的rows上都加了共享锁,这样的话,其他session可以读取这些记录,也可以继续添加IS锁 , 但是无法修改这些记录直到你这个加锁的session执行完成(否则直接锁等待超时) 。 
SELECT ... FOR UPDATE 走的是IX锁(意向排它锁),即在符合条件的rows上都加了排它锁,其他session也就无法在这些记录上添加任何的S锁或X锁 。如果不存在一致性非锁定读的话,那么其他session是无法读取和修改这些记录的,但是innodb有非锁定读(快照读并不需要加锁) , for update之后并不会阻塞其他session的快照读取操作;
除了select ...lock in share mode和select ... for update这种显示加锁的查询操作 。通过对比,发现for update的加锁方式无非是比lock in share mode的方式多阻塞了select...lock in share mode的查询方式,并不会阻塞快照读


推荐阅读