MySQL的四种隔离级别及实现原理

原文链接:https://blog.csdn.net/ln82799/article/details/121025004

1 Read Uncommitted(读未提交)
  在RU级别下,所有的事务都可以看到其他未提交事务所修改的数据,也就是说,在这个隔离级别下会产生脏读和幻读的问题。该级别性能也并不比其他隔离级别好多少,因此很少实际使用。

  • 在「读未提交」隔离级别下,可能发生脏读、不可重复读和幻读现象;

2 Read Committed(提交读)
  在RC级别下,一个事务开始执行后,只能看到其他已经提交的事务造成的修改, 不能看到未提交事务对数据的修改,解决了脏读的问题。可能发生不可重复读和幻读现象。很多DBMS默认隔离级别都是RC。(MySQL除外)

  实现原理: MVCC多版本并发控制

  在每一次进行快照读的时候,都会创建新的ReadView,因此可以读取其他事务提交后的数据

3 Repeatable Read(可重复读)
  在RR级别下,保证同一个事务中多次读取同样的记录的结果是一致的。解决了不可重复读和幻读,可能发生幻读现象。

  实现原理:MVCC和 Next-key Lock

  快照读:当事务第一次对数据进行快照读时,MVCC会创建ReadView,之后的每一次快照读,都会根据之前创建的ReadView进行数据读取,不会读取到其他事务对数据的修改,因此解决了不可重复读和幻读。

  当前读:当事务进行当前读时,会以Next-Key Lock的方式对读取到的数据行进行加锁,这样可以有效防止幻读的发生。Next-Key Lock是Record Lock和Gap Lock的组合。会首先对索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。那样当其他事务便不能对上锁的数据进行修改和插入,保证不会产生幻读!

MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了)解决的方案有两种:

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
  • 针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
    • 举个具体例子,场景如下:

       

       

       

      事务 A 执行了这条当前读语句后,就在对表中的记录加上 id 范围为 (2, +∞] 的 next-key lock(next-key lock 是间隙锁+记录锁的组合)。

      然后,事务 B 在执行插入语句的时候,判断到插入的位置被事务 A 加了 next-key lock,于是事物 B 会生成一个插入意向锁,同时进入等待状态,直到事务 A 提交了事务。这就避免了由于事务 B 插入新记录而导致事务 A 发生幻读的现象。

  Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题,就引出了间隙锁。(读提交隔离级别,是没有间隙锁的,只有记录锁)。

第一个发生幻读现象的场景

  以这张表作为例子:

     

 

   整个发生幻读的时序图如下  

    

  在可重复读隔离级别下,事务 A 第一次执行普通的 select 语句时生成了一个 ReadView,之后事务 B 向表中新插入了一条 id = 5 的记录并提交。接着,事务 A 对 id = 5 这条记录进行了更新操作,在这个时刻,这条新记录的 trx_id 隐藏列的值就变成了事务 A 的事务 id,之后事务 A 再使用普通 select 语句去查询这条记录时就可以看到这条记录了,于是就发生了幻读。

第二个发生幻读现象的场景

  除了上面这一种场景会发生幻读现象之外,还有下面这个场景也会发生幻读现象。

  • T1 时刻:事务 A 先执行「快照读语句」:select * from t_test where id > 100 得到了 3 条记录。
  • T2 时刻:事务 B 往插入一个 id= 200 的记录并提交;
  • T3 时刻:事务 A 再执行「当前读语句」 select * from t_test where id > 100 for update 就会得到 4 条记录,此时也发生了幻读现象。

  要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select ... for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。

可重复读是如何规避幻读的&又是为什么不能完全解决幻读(**)

重复读(下文简称RR)理论上是无法解决幻读的,但通过多版本并发控制(下文简称MVCC)、排它锁(下文简称X锁)、临键锁(Next-Key锁,下文简称NK锁)等去尽量地规避幻读。

一、如何规避幻读。

MVCC有两种读取方式:快照读、当前读。

1、如果只是查询,例如
SELECT * FROM table;
这就是快照读,因为MVCC暗含创建版本号(下文简称CV),删除版本号(下文简称DV),SQL语句暗含了WHERE cv<=当前版本号(即当前事务ID) AND (dv is null OR dv>当前版本号);后面事务插入的数据读不到,这样就不会产生幻读。

2、如果涉及增删改,就使用当前读。读取最新的数据进行处理,即便是后面事务已提交的数据。例如事务1要更新记录A,但是事务2已经删除了记录A,这就会出问题。
由上可见,当前读是避免不了幻读的。因此加入了NK锁来规避。

假设有字段a=10204060、70几条记录。
事务1:
SELECT * FROM table WHERE a=40 FOR UPDATE
这时候InnoDB锁的不仅仅是40这条记录,还会锁(20,40]、(40,60]这两个间隙(GAP)。

事务2,如果INSERT 11或者61,都能成功,但是如果INSERT 21、41就会失败。
所以通过NK锁的排他性,既保证了相关可能产生幻读的记录进不来,也保证了与之无关的数据可以INSERT,提高并发。
二、无法完全避免幻读。 设有如下记录,字段为: id name age cv dv
1 A 10 10 - 2 B 10 10 - 事务100: SELECT * FROM table WHERE age=10; 查询结果为2条记录。 事务200: INSERT INTO table VALUES (3,C,10); 此时表数据变为: id name age cv dv 1 A 10 10 - 2 B 10 10 - 3 C 10 200 - 事务100: SELECT * FROM table WHERE age=10; 查询结果仍为2条记录。因为这两次查询都是快照读。 事务100:不加任何条件,将表所有name改为D, UPDATE table SET name='D'; 因为是当前读,所以修改会作用在所有事务提交的数据上,包括事务200新增的数据3,此时表数据变成了: id name age cv dv 1 A 10 10 100 2 B 10 10 100 3 C 10 200 100 1 D 10 100 - 2 D 10 100 - 3 D 10 100 - 事务100: SELECT * FROM table WHERE age=10; 查询结果为3条记录,出现了幻读。 同理,事务100本来读的是2条,事务100更新之后,也会读出来3条。 事务100前两个SELECT都是快照读,无锁,事务200可以插入,但UPDATE之后,或者假如两个SELECT加了FOR UPDATE,有了NK锁,事务200就无法插入了。

 

小结

MySQL InnoDB 引擎的可重复读隔离级别(默认隔离级),根据不同的查询方式,分别提出了避免幻读的方案:

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读。
  • 针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读。

两个发生幻读场景的例子。

  第一个例子:对于快照读, MVCC 并不能完全避免幻读现象。因为当事务 A 更新了一条事务 B 插入的记录,那么事务 A 前后两次查询的记录条目就不一样了,所以就发生幻读。

  第二个例子:对于当前读,如果事务开启后,并没有执行当前读,而是先快照读,然后这期间如果其他事务插入了一条记录,那么事务后续使用当前读进行查询的时候,就会发现两次查询的记录条目就不一样了,所以就发生幻读。

所以,MySQL 可重复读隔离级别并没有彻底解决幻读,只是很大程度上避免了幻读现象的发生。

https://www.jianshu.com/p/b7c53ee0ed0e

MySQL事务隔离级别中可重复读与幻读

MySQL Repeatable-Read 隔离级别一些误解 

posted @ 2022-06-24 18:07  一人一见  阅读(1302)  评论(0编辑  收藏  举报