幻读和间隙锁

幻读REPEATABLE-READ 重复读 隔离级别下发生在当前读场景,因为重复读有快照这一概念。既事务启动后的第一个语句创建快照,重复读也是默认的隔离级别,因此幻读是一个比较重要的点了。下面会结合或者照搬网络资料的一些案例来加深理解。

数据场景

表结构

1
2
3
4
5
6
7
8
9
10
11
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);

幻读

下面的场景只是脑补,MySQL上的反馈不会和下面一样(MySQL真正运行这些语句有间隙锁来解决下面这些问题),因此实验时并不会真实的发生。

- session A session B session C
T1 begin;
select *from t where d = 5 for update;
result(5,5,5)
T2 update t set d=5 where id =0;
T3 select *frorm t where d = 5 from update;
result(5,5,5)(0,0,5)
T4 insert into t values(1,1,5);
T5 select *frorm t where d = 5 from update;
result(5,5,5)(0,0,5)(1,1,5)
T6 commit
  1. 在session A 中的几条select *frorm t where d = 5 from update;都是加了写锁的SQL语句,他们都使用了当前读
  2. 因为写锁在加的时候还没有相应的数据,所以session B/C的更新个插入操作都没有阻塞
  3. T5的select属于幻读,T3的不是。事务在前后两次查询同一个范围的时候,后一次看到了前一次查询没看到的行是幻读,幻读针对的是新插入的行,而不是当前读改变的行数据

幻读的问题

破坏语义

- session A session B session C
T1 begin;
select *from t where d = 5 for update;
T2 update t set d=5 where id =0;
update t set c=5 where id =0;
T3 select *frorm t where d = 5 from update;
T4 insert into t values(1,1,5);
update t set c=5 where id =1;
T5 select *frorm t where d = 5 from update;
T6 commit

在session B/C 中新加的2句SQL就是破坏了session A 中的语义,for update写锁,不让修改加锁的数据。但是在加锁时session B/C的数据还没有,语句破坏了for update不让修改的加锁声明

破坏数据一致性

- session A session B session C
T1 begin;
select *from t where d = 5 for update;
update t set d =100 where d =5;
T2 update t set d=5 where id =0;
update t set c=5 where id =0;
T3 select *frorm t where d = 5 from update;
T4 insert into t values(1,1,5);
update t set c=5 where id =1;
T5 select *frorm t where d = 5 from update;
T6 commit

分析结果 数据结果

session A : (5,5,100)
session B : (0,5,5)
session C : (1,5,5)

从上面来看数据是没问题的,那么我们的binlog日志该如何记录呢

如果保持隔离不加锁

1
2
3
4
5
6
7
8

update t set d=5 where id =0;
update t set c=5 where id =0;

insert into t values(1,1,5);
update t set c=5 where id =1;

update t set d =100 where d =5; //`这句会破坏数据一致性`

这份binlog回滚后数据明显不对,update t set d =100 where d =5 同时更新了 session B 插入的那一句,回滚结果出现了不一致pass。


如果把seesionA扫描过程中的语句都加写上锁 d = 5 那么SQL执行顺序如下

1
2
3
4
5
6
7
8
9

insert into t values(1,1,5);
update t set c=5 where id =1;

update t set d =100 where d =5;//这句又有问题了

update t set d=5 where id =0;
update t set c=5 where id =0;

这份binlog回滚后数据,d=5的这一次更新又会造成数据的不一致。

间隙锁(gap lock)

行锁只能锁住行,但是新插入记录这个动作要更新的是记录之间的间隙,因此,为了解决幻读,InnoDB引入了间隙锁这个概念,并且间隙锁是在可重复读下的产物

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~