20 | 幻读是什么,幻读有什么问题?


MySQL45讲

实践篇

20 | 幻读是什么,幻读有什么问题?

示例:

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);
“幻读”是什么?

在这里插入图片描述

  • Q1 只返回 id=5 这一行;
  • 在 T2 时刻,session B 把 id=0 这一行的 d 值改成了 5,因此 T3 时刻 Q2 查出来的是 id=0 和 id=5 这两行;
  • 在 T4 时刻,session C 又插入一行(1,1,5),因此 T5 时刻 Q3 查出来的是 id=0、id=1 和 id=5 的这三行。

“幻读”指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。(前后数据行数不一致)

“幻读”的说明:

  • 在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,“幻读”在“当前读”下才会出现。
  • 上面 session B 的修改结果,被 session A 之后的 select 语句用“当前读”看到,不能称为幻读。幻读仅专指“新插入的行”。
“幻读”有什么问题?
  • 语义被破坏
    在这里插入图片描述
    T1 时刻,session A 只是给 id=5 这一行加了行锁, 并没有给 id=0 这行加上锁。因此,session B 在 T2 时刻,可以执行这两条 update 语句。这样,就破坏了 session A 里 Q1 语句要锁住所有 d=5 的行的加锁声明。
  • 数据一致性的问题
    锁的设计是为了保证数据的一致性。而这个一致性,不止是数据库内部数据状态的一致性,还包含了数据和日志在逻辑上的一致性
    session C 也是一样的道理,对 id=1 这一行的修改,也是破坏了 Q1 的加锁声明。
    在这里插入图片描述
    执行完成后,数据库里的结果:
  • 经过 T1 时刻,id=5 这一行变成 (5,5,100);
  • 经过 T2 时刻,id=0 这一行变成 (0,5,5);
  • 经过 T4 时刻,表里面多了一行 (1,5,5);
  • 其他行跟这个执行序列无关,保持不变。

binlog 里面的内容:

  • T2 时刻,session B 事务提交,写入了两条语句;
  • T4 时刻,session C 事务提交,写入了两条语句;
  • T6 时刻,session A 事务提交,写入了 update t set d=100 where d=5 这条语句。
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 t set d=100 where d=5;/*所有d=5的行,d改成100*/

这个语句序列,不论是拿到备库去执行,还是以后用 binlog 来克隆一个库,这三行的结果,都变成了 (0,5,100)、(1,5,100) 和 (5,5,100)。

显然,id=0 和 id=1 这两行,发生了数据不一致。

如何解决幻读?

InnoDB 引入间隙锁 (Gap Lock),来解决“幻读”问题。

在这里插入图片描述

和间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。 间隙锁之间都不存在冲突关系。

在这里插入图片描述

因为表 t 里并没有 c=7 这个记录,因此 session A 加的是间隙锁 (5,10)。而 session B 也是在这个间隙加的间隙锁。

间隙锁和行锁合称 next-key lock,每个 next-key lock 是前开后闭区间。

间隙锁会导致死锁。

在这里插入图片描述

  1. session A 执行 select … for update 语句,由于 id=9 这一行并不存在,因此会加上间隙锁 (5,10);
  2. session B 执行 select … for update 语句,同样会加上间隙锁 (5,10),间隙锁之间不会冲突,因此这个语句可以执行成功;
  3. session B 试图插入一行 (9,9,9),被 session A 的间隙锁挡住,只好进入等待;
  4. session A 试图插入一行 (9,9,9),被 session B 的间隙锁挡住。

间隙锁是在可重复读隔离级别下才会生效的。 所以,如果把隔离级别设置为读提交的话,就没有间隙锁了。但同时,要解决可能出现的数据和日志不一致问题,需要把 binlog 格式设置为 row。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久违の欢喜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值