到底什么是幻读,幻读究竟能造成什么问题

Posted 刘俊岐.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了到底什么是幻读,幻读究竟能造成什么问题相关的知识,希望对你有一定的参考价值。

首先,为了说明白这个问题,我们先建一个表。

create table t(
	id int NOT NULL,
	c int DEFAULT NULL,
  d int 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);

现在我们执行这样一行语句。

select * from t where d = 5 for update;

我们很容易得到,由于字段d上没有加上索引,那么这条语句就会使用全表扫描,然后就会锁住主键为5的这一行,那么其他的行会不会锁住呢?

什么是幻读

我们举个例子(和实际发生的不一样)

sessionAsessionBsessionC
T1begin:
T2select * from t where d =5 for update;
result :(5,5,5)
T3update t set d =5 where id =0;
T4select * from t where d =5 for update;
result :(0,0,5),(5,5,5)
T5insert into t values(1,1,5)
T6select * from t where d =5 for update;
result :(0,0,5),(1,1,5),(5,5,5)
T7commit;

而幻读就发生在T6这个时候。

在可重复读级别下,普通的查询都是快照读,是看不到幻读这个现象的,幻读的现象都是发生在当前读。

上面T4的结果不能成为幻读,幻读专门指“新插入的行”。

那么按我上述说的这样执行有问题吗?

幻读所带来的问题

语义上的问题

首先,我们都知道select * from t where d =5 for update;这行语句的语义是锁住所有d=5的行不允许操作,但是sessionC却对d=5这一行进行了操作,˙这河里吗???

数据一致性上的问题

为了说明这个问题,我将上述例子改一下。

sessionAsessionBsessionC
T1begin:
T2select * from t where d =5 for update;
result :(5,5,5)
update set d=100 where d=5;
T3update t set d =5 where id =0;
update t set c =5 where id =0;
T4select * from t where d =5 for update;
result :(0,0,5),(5,5,5)
T5insert into t values(1,1,5);
update t set c =5 where id =1;
T6select * from t where d =5 for update;
result :(0,0,5),(1,1,5),(5,5,5)
T7commit;

这样,表中数据经过上述操作后就变成了

idcd
055
155
55100
101010
151515
202020

现在看还没什么问题,但是我们不要忽略了,当需要主从同步的时候,是需要binlog的,我们再来看下binlog中的值。我们按提交的顺序依次写入binlog。

update t set d =5 where id =0; (0,0,5)
update t set c =5 where id =0; (0,5,5)
insert into t values(1,1,5); (1,1,5)
update t set c =5 where id =1; (1,5,5)
update set d=100 where d=5; 这时候就出问题了,将所有d=5的行的值都改成了100,主从不一致了,这肯定不行。

从上述描述中我们可以发现,的确这样会产生问题,那么这个问题是如何产生的呢?

这个问题的产生就在于我们认为select * from t where d =5 for update;这个语句只给d=5这一行加锁了!

那么如果我们将我们所有扫描到的行都加锁行不行呢?

然后我们发现,我们tmd插入了一行(1,1,5),这一行我们访问不到啊!也就是加不了锁。看来这样也解决不了问题。那么究竟该如何解决幻读呢?

间隙锁

我们发现,产生幻读的原因是InnoDB目前只能锁住行,为了解决幻读这个问题,InnoDB只能引入了新的锁——间隙锁。

0510152025
(-inf,0)(0,5)(5,10)(10,15)(15,20)(20,25)(25,+inf)

当我们执行了select * from t where d =5 for update;这条语句时,不仅仅给这6行记录加了锁,同时也给中间7个间隙加了锁。

这样通过间隙锁我们就解决了幻读。

间隙锁加锁规则

需要注意的是,间隙锁的加锁规则和我们说的行锁的加锁规则不太一样。
间隙锁间隙锁不是冲突的,与间隙锁冲突的是向这个间隙中插入一条记录,插入记录的行为会加插入意向锁!!插入意向锁和间隙锁冲突,而update只是加行锁,和间隙锁不冲突

举个例子

sessionAsessionB
begin;
select * from t where c =7 for update;
begin;
select * from t where c =7 for update;

这两个语句就不是冲突的。他们都锁住了(5,10)这个间隙,他们都是为了保护这个间隙被插入记录。

next-key lock

间隙锁+行锁就形成了next-key lock,这个区间是前开后闭区间。

以上是关于到底什么是幻读,幻读究竟能造成什么问题的主要内容,如果未能解决你的问题,请参考以下文章

到底什么是幻读,幻读究竟能造成什么问题

到底什么是幻读,幻读究竟能造成什么问题

mysql随手记-innodb幻读

幻读是啥, 幻读有啥问题

面试官:MySQL的幻读是怎么被解决的?

谈谈MySQL是如何解决幻读问题的?