MySQL InnoDB RR隔离级别是否能够避免幻读?

背景

在MySQL的InnoDB引擎中,支持四种事务隔离级别,分别如下:

1、READ UNCOMMITED(未提交读):使用查询语句不会加锁,允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。

2、READ COMMITED(提交读):只能读取到已经提交的数据,只对记录加记录锁,而不会在记录之间加间隙锁,所以允许新的记录插入到被锁定记录的附近,所以再多次使用查询语句时,可能得到不同的结果(Non-Repeatable Read)。

3、REPEATABLE READ(可重复读):多次读取同一范围的数据会返回第一次查询的快照,不会返回不同的数据行,但是可能发生"幻读(Phantom Read)?? "

4、SERIALIZABLE(串行读):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

下面对上面四种隔离级别进行简单的举例说明:

1、未提交读:也可以叫做脏读,就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

2、提交读:也可以叫做不可重复读,是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

3、可重复读:第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

4、串行读:这个比较好理解,即独占锁,读写都会阻塞,会大大降低并发量。

在InnoDB中,默认的事务隔离级别是RR(REPEATABLE READ),对于RR隔离级别是否能够阻止幻读,网上的文章有很多的争论,本篇,我们就这个问题进行展开,来聊一下RR隔离级别是否能够避免幻读

RR隔离级别是否能够阻止幻读

网上的很多博客中,都说InnoDB的RR事务隔离级别中,使用了MVCC机制来避免了幻读,我们下面来做几个实验,同时结合官方文档的说法,来进行验证。

实验环境:MySQL8.0,事务隔离级别:RR

mysql> select @@global.transaction_isolation,@@transaction_isolation;
+--------------------------------+-------------------------+
| @@global.transaction_isolation | @@transaction_isolation |
+--------------------------------+-------------------------+
| REPEATABLE-READ                | REPEATABLE-READ         |
+--------------------------------+-------------------------+
1 row in set (0.00 sec)

创建一张测试表:

mysql> desc test_table;
+-----------+-------------+------+-----+---------+-------+
| Field     | Type        | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| id        | bigint      | NO   | PRI | NULL    |       |
| user_name | varchar(50) | YES  |     | NULL    |       |
+-----------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

实验A:

两个事务A、B,RR隔离级别下的快照读,出现“幻读”。

执行结果A

我们开启两个事务Session,在SessionA中进行查询操作,此时没有记录;

在SessionB中进行新增记录,并提交;

在SessionA中再次进行查询操作,并没有查询到SessionB的插入记录;

在SessionA中尝试插入同样主键的记录,发现该记录已经存在,仿佛出现了“幻觉”。

实验B:

两个事务A、B,RR隔离级别下的快照读,更新时发生"幻读"。
执行结果B
本事务中第一次读取出一行,做了一次更新后,另一个事务里提交的数据就出现了。也可以看做是一种幻读。

我们开启两个事务Session,在SessionA中进行查询操作,此时没有记录;

在SessionB中进行新增记录,并提交;

在SessionA中再次进行查询操作,并没有查询到SessionB的插入记录;

在SessionA中尝试做了一次更新后,SessionB里提交的数据就出现了,也可以看做是一种幻读。

MySQL InnoDB如何定义幻读

经过上面的两个实验,我们发现在InnoDB的RR隔离级别场景下,是可能出现"幻读"的情况,那么我们界定的幻读,是否与MySQL定义的"幻读"是同一个概念呢?

我们来看一下官方对幻读(phantom)的定义:
https://dev.mysql.com/doc/refman/8.0/en/innodb-next-key-locking.html

The so-called phantom problem occurs within a transaction when the same query produces 
different sets of rows at different times. 
For example, if a SELECT is executed twice, but returns a row the second time 
that was not returned the first time, the row is a “phantom” row.

MySQL官方对幻读(phantom)的定义是:如果在一个事务中,在不同的时间执行查询返回的结果不同,则称为幻读(phantom)

由此发现,上述实验中,我们认为的"幻读",对于MySQL来说,并非"幻读",而是快照读。

https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html

By default, InnoDB operates in REPEATABLE READ transaction isolation level. 
In this case, InnoDB uses next-key locks for searches and index scans, 
which prevents phantom rows (see Section 15.7.4, “Phantom Rows”).

根据官方文档的说法,在RR事务隔离级别下,在当前读的场景下,在搜索和扫描索引的时候使用的next-key locks可以避免幻读(phantom)

由此我们可以得到结论:

  • 在快照读情况下,MySQL通过MVCC来避免幻读。
  • 在当前读情况下,MySQL通过next-key来避免幻读。

“读”与“读”的区别

在InnoDB的RR隔离级别中,有两种读,非常容易混淆,我们来拆解一下。

默认的情况下,我们叫它快照读 (snapshot read),而读取数据库当前版本数据的方式,叫当前读 (current read)。很显然,在MVCC中:

快照读:就是select
select * from table ….;

当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update ;
delete;

MySQL InnoDB的Next-Key Locks

经过上述的解读,我们知道InnoDB的RR隔离级别下,在当前读的场景下,通过加next-key locks来避免幻读。

那么什么是next-key locks

我们看一下官方文档给出的解释:

A next-key lock is a combination of a record lock on the index record 
and a gap lock on the gap before the index record.

InnoDB performs row-level locking in such a way that when it searches or scans a table index, 
it sets shared or exclusive locks on the index records it encounters. 

Thus, the row-level locks are actually index-record locks. 
A next-key lock on an index record also affects the “gap” before that index record. 

That is, a next-key lock is an index-record lock plus a gap lock on the gap preceding the index record.

If one session has a shared or exclusive lock on record R in an index, 
another session cannot insert a new index record in the gap immediately before R in the index order.

简单来说,next-key locks是一种间隙锁,相较于行锁只锁定一行,它会锁定一个区间范围,加锁的范围区间内的索引键值会被锁定,其使用方式如下:

SELECT * FROM child WHERE id > 100 FOR UPDATE;

这样,InnoDB会给id大于100的行(假如child表里有一行id为102),以及100-102,102+的gap都加上锁。

需要注意的是,当加入FOR UPDATE语句后,则从快照读变为了当前读。

了解了next-key locks的定义和使用方式,我们再来做两个实验验证一下是否是这样子的。

实验C:

两个事务A、B,RR隔离级别下,启用间隙锁。
执行结果C
可以看到,我们对id<=1的范围进行了加锁,insert id为0的记录时就会被阻塞,一直等待锁的释放。

在InnoDB中,同时也提供了加锁读来获取最新内容的机制,官方文档对此的说法:

If you want to see the “freshest” state of the database, 
use either the READ COMMITTED isolation level or a locking read.

SELECT * FROM t FOR SHARE;

我们来验证一下。

实验D:

两个事务A、B,RR隔离级别下,不加锁读结果为快照读,加锁读取结果为当前读。

执行结果D
执行结果D
由上面的执行结果可以看到,如果使用普通的读(MVCC),会得到一致性的结果;

如果使用了加锁的读,就会读到“最新的”提交读的结果。

本身,可重复读和提交读是矛盾的。在同一个事务里,如果保证了可重复读,就会看不到其他事务的提交,违背了提交读;

如果保证了提交读,就会导致前后两次读到的结果不一致,违背了可重复读。

结论

在MySQL InnoDB中:

RR事务隔离级别是通过MVCC机制解决了幻读;

在当前读读情况下,通过next-key locks可以避免幻读。

本篇参考:
https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html

https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html

https://dev.mysql.com/doc/refman/8.0/en/innodb-next-key-locking.html

https://github.com/Yhzhtk/note/issues/42

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,InnoDB默认隔离级别下(REPEATABLE READ)存在幻读的问题。 幻读是指在同一个事务内,多次执行相同的查询,但结果集却不一致的情况。这是由于其他事务在查询期间插入或删除了满足查询条件的行导致的。 为了解决幻读问题,InnoDB引入了多版本并发控制(Multi-Version Concurrency Control,简称MVCC)机制,并提供了两种解决幻读问题的方式: 1. 快照读(Snapshot Read):在REPEATABLE READ隔离级别下,默认使用快照读。快照读会在事务开始时创建一个一致性视图,并使用该视图来读取数据。其他事务对数据的修改不会影响当前事务的读取操作,从而避免幻读问题。 2. 当前读(Current Read):在REPEATABLE READ隔离级别下,可以使用当前读来解决幻读问题。当前读会对查询的数据加锁,确保其他事务不能插入或删除符合查询条件的行。可以使用SELECT ... FOR UPDATE语句或SELECT ... LOCK IN SHARE MODE语句来进行当前读操作。 需要注意的是,REPEATABLE READ隔离级别下,使用快照读可以避免大部分幻读问题,但在某些情况下仍然可能出现幻读。如果需要完全避免幻读,可以将隔离级别提升至SERIALIZABLE,但这可能会影响并发性能。 总结起来,InnoDB通过MVCC机制和快照读、当前读的方式来解决幻读问题。开发者可以根据具体的业务需求和性能要求选择适当的隔离级别和读取方式来处理幻读问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值