到底什么是幻读,幻读究竟能造成什么问题
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的这一行,那么其他的行会不会锁住呢?
什么是幻读
我们举个例子(和实际发生的不一样)
sessionA | sessionB | sessionC | |
---|---|---|---|
T1 | begin: | ||
T2 | select * from t where d =5 for update; result :(5,5,5) | ||
T3 | update t set d =5 where id =0; | ||
T4 | select * from t where d =5 for update; result :(0,0,5),(5,5,5) | ||
T5 | insert into t values(1,1,5) | ||
T6 | select * from t where d =5 for update; result :(0,0,5),(1,1,5),(5,5,5) | ||
T7 | commit; |
而幻读就发生在T6这个时候。
在可重复读级别下,普通的查询都是快照读,是看不到幻读这个现象的,幻读的现象都是发生在当前读。
上面T4的结果不能成为幻读,幻读专门指“新插入的行”。
那么按我上述说的这样执行有问题吗?
幻读所带来的问题
语义上的问题
首先,我们都知道select * from t where d =5 for update;
这行语句的语义是锁住所有d=5的行不允许操作,但是sessionC却对d=5这一行进行了操作,˙这河里吗???
数据一致性上的问题
为了说明这个问题,我将上述例子改一下。
sessionA | sessionB | sessionC | |
---|---|---|---|
T1 | begin: | ||
T2 | select * from t where d =5 for update; result :(5,5,5) update set d=100 where d=5; | ||
T3 | update t set d =5 where id =0; update t set c =5 where id =0; | ||
T4 | select * from t where d =5 for update; result :(0,0,5),(5,5,5) | ||
T5 | insert into t values(1,1,5); update t set c =5 where id =1; | ||
T6 | select * from t where d =5 for update; result :(0,0,5),(1,1,5),(5,5,5) | ||
T7 | commit; |
这样,表中数据经过上述操作后就变成了
id | c | d |
---|---|---|
0 | 5 | 5 |
1 | 5 | 5 |
5 | 5 | 100 |
10 | 10 | 10 |
15 | 15 | 15 |
20 | 20 | 20 |
现在看还没什么问题,但是我们不要忽略了,当需要主从同步的时候,是需要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只能引入了新的锁——间隙锁。
0 | 5 | 10 | 15 | 20 | 25 | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
(-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只是加行锁,和间隙锁不冲突
举个例子
sessionA | sessionB |
---|---|
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
,这个区间是前开后闭区间。
以上是关于到底什么是幻读,幻读究竟能造成什么问题的主要内容,如果未能解决你的问题,请参考以下文章