MySQL45学习幻读

  • 1.什么是幻读
  • 幻读的例子:
  • 2. 为什么要防止幻读
  • 3. 怎么幻读是怎么解决的
  • 例子
  • 例1:
  • 例2:
  • 例3:


1.什么是幻读

幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行

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

幻读仅专指“新插入的行”

幻读的例子:

  • 假设有一个幻读的情况:
  • 表 T(id,a) 里面只有一行数据 row(id=1,a=1,b=1)

时间顺序

事务1

事务2

time1

select * from T where a = 1 for update (id=1,a=1)

time2

什么都不做

insert(id=2,a=1);

time3

select * from T where a = 1 for update (id=1,a=1) (id=2,a=1)

  • time1的查询结果是(id=1,a=1)
  • time3的查询结果是(id=1,a=1),(id=2,a=1)
  • 同一个事务之中两次查询的结果不一致

2. 为什么要防止幻读

If we regard a set of rows as a data item, the new phantom child would violate the isolation principle of transactions that a transaction should be able to run so that the data it has read does not change during the transaction.

因为幻读违背了事务隔离原则
例子中的情况,按道理来说应该在a=1的数据上加锁,但是这里
事务在执行期间,事务读取到的数据本应该是不会变化的

3. 怎么幻读是怎么解决的

MySQL使用临键锁来解决幻读的问题,MySQL锁介绍

  • 临键锁 = 记录锁 + 间隙锁
  • 假设有个事务T1,进行了查询(当强读就是select ... for update)操作
  • MySQL会根据where条件,扫描索引(如果没有普通索引则扫描聚簇索引)
  • 扫描过的索引范围上加上记录锁间隙锁
  • 其他事务想要插入,得在插入操作之前加锁
  • 但是因为where条件扫描过的范围上已经被加上了间隙锁记录锁,所以insert操作会被阻塞
  • 必须等到事务T1释放锁,才能执行成功

例子

  • 设有表t,表中只有三列(id,a,b),id是主键,a上有索引
  • 表里有2条数据(id=1,a=1,b=1)(id=10,a=10,b=10)

例1:

T1:
start transaction;
select * from t where a > 5 for update;
  • 当前读语句会在a的索引上加三个锁
  1. 间隙锁:(1,10)开区间
  2. 记录锁:a=10
  3. 间隙锁:(10,supremum)开区间

例2:

T1:
start transaction;
select * from t where a = 10 for update;
  • 当前读语句会在a的索引上加三个锁
  1. 间隙锁:(1,10)开区间
  2. 记录锁:a=10
  3. 间隙锁:(10,supremum)开区间

例3:

T1:
start transaction;
select * from t where b = 10 for update;
  • 当前读语句会在id聚簇索引上加5个锁
  1. 间隙锁:(infimum,1)开区间
  2. 记录锁:id=1
  3. 间隙锁:(1,10)开区间
  4. 记录锁:id=10
  5. 间隙锁:(10,supremum)开区间