MySQL45学习幻读
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,10)开区间
- 间隙锁:
-
- 记录锁:
a=10
- 记录锁:
-
- 间隙锁:
(10,supremum)开区间
- 间隙锁:
例2:
T1:
start transaction;
select * from t where a = 10 for update;
- 当前读语句会在a的索引上加三个锁
-
- 间隙锁:
(1,10)开区间
- 间隙锁:
-
- 记录锁:
a=10
- 记录锁:
-
- 间隙锁:
(10,supremum)开区间
- 间隙锁:
例3:
T1:
start transaction;
select * from t where b = 10 for update;
- 当前读语句会在id聚簇索引上加5个锁
-
- 间隙锁:
(infimum,1)开区间
- 间隙锁:
-
- 记录锁:
id=1
- 记录锁:
-
- 间隙锁:
(1,10)开区间
- 间隙锁:
-
- 记录锁:
id=10
- 记录锁:
-
- 间隙锁:
(10,supremum)开区间
- 间隙锁: