mysql是怎样解决幻读的?

390 阅读4分钟

什么是幻读

以下例子都默认可重复读的隔离级别下

CREATE TABLE t (

id int(11) NOTNULL,

c int(11) DEFAULTNULL,

d int(11) DEFAULTNULL,

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)

在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现。

InnoDB通过间隙锁和行锁的方式解决了幻读,下面举2个例子来反证为什么需要间隙锁(实际中不会存在的)

1、假如只锁当前行

image.png

会发现q3会查到新增的1,1,5。产生了幻读(q2不能算幻读,幻读专指新增或删除的记录)

幻读产生的问题

1、语义上的

image.png q1表示将所有d=5的记录锁住,但是没有锁住id=0的,所以session b的2条update语句可以执行。
session B的第二条语句update t set c=5 where id=0,语义是我把id=0、d=5这一行的c值,改成了5。
这样,就破坏了 session A 里Q1语句要锁住所有d=5的行的加锁声明。session c也是一样

2、数据一致性

image.png

现在,我们来分析一下图3执行完成后,数据库里会是什么结果。

  1. 经过T1时刻,id=5这一行变成 (5,5,100),当然这个结果最终是在T6时刻正式提交的;

  2. 经过T2时刻,id=0这一行变成(0,5,5);

  3. 经过T4时刻,表里面多了一行(1,5,5);

  4. 其他行跟这个执行序列无关,保持不变。

这样看,这些数据也没啥问题,但是我们再来看看这时候binlog里面的内容。

  1. T2时刻,session B事务提交,写入了两条语句;

  2. T4时刻,session C事务提交,写入了两条语句;

  3. T6时刻,session A事务提交,写入了update t set d=100 where d=5 这条语句。

会发现binlog记录的顺序是有问题的,如果拿这个binlog去执行会和数据库的数据产生不一致的问题。

发现锁上当前行会产生幻读,那么锁上所有行呢?

假如锁上所有扫描的行

image.png

binLog执行顺序如下

insert into t values(1,1,5); /(1,1,5)/

update t set c=5 where id=1; /(1,5,5)/

update t set d=100 where d=5;/所有d=5的行,d改成100/

update t set d=5 where id=0; /(0,0,5)/

update t set c=5 where id=0; /(0,5,5)/

可以看出锁住了所有行,所有session b会被阻塞住,那么它在binlog中的顺序和执行的顺序保持一致,那么不会产生一致性问题。但是加锁的时候id=1这行都不存在,那么也就加不上锁,自然也阻止不了他的新增,那么还是会产生幻读问题。

间隙锁(GapLock)

行锁解决不了幻读问题,那么通过锁住可能会插入的空隙,来阻塞住update或者insert语句。既然阻塞住了更新和新增语句,那么自然也不会有幻读问题了。 间隙锁就是锁住2个值间的空隙

image.png 执行 select *fromt where d=5 for update的时候,就不止是给数据库中已有的6个记 录加上了行锁,还同时加了7个间隙锁。这样就确保了无法再插入新的记录。

与读写锁不同的是,间隙锁和间隙锁之前是没有冲突的(不像读锁和写锁之间冲突)。跟间隙锁存在冲突关系的,是 跟 “往这个间隙中插入一个记录” 这个操作,如果插入的记录在被锁住的范围,那么会冲突。

next-key lock:间隙锁+行锁

如:select *fromt for update要把整个表所有记录锁起来,就形成了7个next-key lock,分别是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。

加锁规则

  1. 原则1:加锁的基本单位是next-key lock。next-key lock是前开后闭区间。
  2. 原则2:查找过程中访问到的对象才会加锁。
  3. 优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁。
  4. 优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁。
  5. 一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。