面试精选

MySQL - 幻读 、间隙锁

2021-08-03  本文已影响0人  lconcise

select for update 加写锁。事物提交才释放

什么是幻读?

产生幻读的场景:

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

幻读说明:

  1. 在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现
  2. 上面 session B 的修改结果,被 session A 之后的 select 语句用“当前读”看到,不能称为幻读。幻读仅专指“新插入的行”

幻读的问题?

  1. 首先是语义上的。session A 在 T1 时刻就声明了,“我要把所有 d=5 的行锁住,不准别的事务进行读写操作”。而实际上,这个语义被破坏了。
  2. 锁的设计是为了保证数据的一致性。而这个一致性,不止是数据库内部数据状态在此刻的一致性,还包含了数据和日志在逻辑上的一致性。
    也就是说,即使把所有的记录都加上锁,还是阻止不了新插入的记录

解决幻读

产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。

间隙锁,锁的就是两个值之间的空隙

行锁有冲突关系的是“另外一个行锁”。但是间隙锁不一样,跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。间隙锁之间都不存在冲突关系。

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

间隙锁和 next-key lock 的引入,帮我们解决了幻读的问题,但同时也带来了一些“困扰”。

间隙锁的引入,可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的。

实践

表初始化

CREATE TABLE z (
  id INT PRIMARY KEY AUTO_INCREMENT,
  b  INT,
  KEY b(b)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO z 
  (id, b)
VALUES 
  (1, 2),
  (3, 4),
  (5, 6),
  (7, 8),
  (9, 10);
image.png
BEGIN;
SELECT * FROM z WHERE b = 6 FOR UPDATE;
INSERT INTO z VALUES (2, 4);/*success*/
INSERT INTO z VALUES (2, 8);/*blocked*/
INSERT INTO z VALUES (4, 4);/*blocked*/
INSERT INTO z VALUES (4, 8);/*blocked*/
INSERT INTO z VALUES (8, 4);/*blocked*/
INSERT INTO z VALUES (8, 8);/*success*/
INSERT INTO z VALUES (0, 4);/*blocked*/
INSERT INTO z VALUES (-1, 4);/*success*/

分别执行 session B中的insert 会出现上述情况,为什么?

加锁过程

  1. 在索引 b 上的等值查询,给索引 b 加上了 next-key lock (4, 6];索引向右遍历,且最后一个值不满足条件时退化为间隙锁;所以会再加上间隙锁 (6,8);所以索引 b 上的 next-key lock 的范围是(b=4,id=3)到(b=6,id=5)这个左开右闭区间和(b=6,id=5)到(b=8,id=7)这个开区间。(读起来有点绕口,看不懂的可以看下文中的图)
  2. for update 会给 b = 6 这一行加上行锁;因此 (b=6,id=5) 这一行上有行锁
image.png

参考博文:
https://www.jianshu.com/p/42e60848b3a6
https://blog.csdn.net/zcl_love_wx/article/details/82382582

上一篇下一篇

猜你喜欢

热点阅读