Skip to content

Innodb 中 RR 隔离级别能否防止幻读? #42

Open
@Yhzhtk

Description

@Yhzhtk
Owner

问题引出

我之前的一篇博客 数据库并发不一致分析 有提到过事务隔离级别以及相应加锁方式、能够解决的并发问题。

标准情况下,在 RR(Repeatable Read) 隔离级别下能解决不可重复读(当行修改)的问题,但是不能解决幻读的问题。

而之前有看过一篇 mysql 加锁的文章 MySQL 加锁处理分析,里面有提到一点:

对于Innodb,Repeatable Read (RR) 针对当前读,RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象

那么问题来了,到底 Innodb 中 RR 隔离级别是否能解决幻读呢?

在 MySQL 加锁处理分析这篇文章下面的评论中,有这样的一个交流:

ontheway
弱弱地问一句,我看的书里面都说的是RR隔离级别不允许脏读和不可重复读,但是可以幻读,怎么和作者说的不一样呢?

hedengcheng(作者)
你说的没错,因此我在文章一开始,就强调了这一点。mysql innodb引擎的实现,跟标准有所不同。

求证官方文档

MySQL Innodb 引擎的实现,跟标准有所不同,针对这个问题,我表示怀疑,于是查看 mysql 官方文档关于 RR的解释,里面有这么一段话:

For locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE), UPDATE, and DELETE statements, locking depends on whether the statement uses a unique index with a unique search condition, or a range-type search condition. For a unique index with a unique search condition, InnoDB locks only the index record found, not the gap before it. For other search conditions, InnoDB locks the index range scanned, using gap locks or next-key locks to block insertions by other sessions into the gaps covered by the range.

大致意思就是,在 RR 级别下,如果查询条件能使用上唯一索引,或者是一个唯一的查询条件,那么仅加行锁,如果是一个范围查询,那么就会给这个范围加上 gap 锁或者 next-key锁 (行锁+gap锁)。

从这句话的理解来看,和文章里的解释一样,由于 RR 级别对于范围会加 GAP 锁,这个和 sql 的标准是有一些差异的。

其他解释

后面又发现了一篇文章 Understanding InnoDB transaction isolation levels,文章中又提到:

This isolation level is the default for InnoDB. Although this isolation level solves the problem of non-repeatable read, but there is another possible problem phantom reads.

大概意思是,RR 能解决不可重复读的问题,但仍可能发生幻读,怀疑作者并不了解 Innodb 的特殊实现,评论中也有提到:

Do you mean 'write skew' instead of 'phantom reads'? The 'repeatable read' in SQL standard allows 'phantom reads', however, since InnoDB uses next-key locking this anomaly does not exist in this level. Looks like it's equivalent to 'snapshot isolation' in Postgres and Oracle.

再来看一篇文章 MySQL的InnoDB的幻读问题,这里面提供了一些例子,还没来得及分析,但最后的结论是:

MySQL InnoDB的可重复读并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是next-key locks。

最终结论

Innodb 的 RR 隔离界别对范围会加上 GAP,理论上不会存在幻读,但是是否有例外呢,这个还需要进一步求证。

Activity

hexufeng

hexufeng commented on Jan 25, 2017

@hexufeng

很好的总结,谢谢。很多人由于看到innodb的RR级别不发生幻读,所以就以为sql标准中RR是不发生幻读的,甚至有些搞培训的都这么认为!

xiaoma20082008

xiaoma20082008 commented on Mar 22, 2017

@xiaoma20082008

maybe you try it like this
rr

fqdeng

fqdeng commented on Apr 17, 2017

@fqdeng

@xiaoma20082008
所以这样还是没法避免 幻读的情况对吧,
但是之前博主的博客,不是说 select * from t where a = 1;
虽然a不是主键,也不存在唯一索引,但是会对a 符合条件的行 加上gap锁么?
这样事务2应该会阻塞,而不是update成功才对,事务1也不会读到事务2的更新才是

lc87624

lc87624 commented on Jun 2, 2017

@lc87624

@jonwinters,@xiaoma20082008 举的例子中,第一次的"select * from t where a = 1"是快照读,而不是当前读,所以是不会加锁的,事务2也不会阻塞。但是我认为这个例子反映的并不是幻读的问题。
引用MySQL 加锁处理分析里的定义:

所谓幻读,就是同一个事务,连续做两次当前读 (例如:select * from t1 where id = 10 for update;),那么这两次当前读返回的是完全相同的记录 (记录数量一致,记录本身也一致),第二次的当前读,不会比第一次返回更多的记录 (幻象)。

幻读对比的是两次当前读返回的结果,@xiaoma20082008 例子里对比的是一次快照读和一次当前读,这个不是幻读的问题。

fqdeng

fqdeng commented on Jun 5, 2017

@fqdeng

@lc87624
事务里面有快照读跟当前读,soga
我一直没搞清楚这个问题,刚才看了mvcc搞懂了 多谢,,

liuxiaoyu8858

liuxiaoyu8858 commented on Jun 26, 2017

@liuxiaoyu8858

MVCC是实现的是快照读,next-key locking 是对当前读 都可以避免幻读

hao5ang

hao5ang commented on Sep 19, 2017

@hao5ang

感觉这篇文章讲的不错: http://blog.sina.com.cn/s/blog_499740cb0100ugs7.html
Innodb 要想避免幻读, 需要加锁读.
"如果使用普通的读,会得到一致性的结果,如果使用了加锁的读,就会读到“最新的”“提交”读的结果。"

ChenHaoyuan

ChenHaoyuan commented on Sep 24, 2018

@ChenHaoyuan

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官方给出的幻读解释是:只要在一个事务中,第二次select多出了row就算幻读。
a事务先select,b事务insert确实会加一个gap锁,但是如果b事务commit,这个gap锁就会释放(释放后a事务可以随意dml操作),a事务再select出来的结果在MVCC下还和第一次select一样,接着a事务不加条件地update,这个update会作用在所有行上(包括b事务新加的),a事务再次select就会出现b事务中的新行,并且这个新行已经被update修改了,实测在RR级别下确实如此。

如果这样理解的话,Mysql的RR级别确实防不住幻读

nvidi

nvidi commented on Dec 24, 2018

@nvidi

在快照读读情况下,mysql通过mvcc来避免幻读。
在当前读读情况下,mysql通过next-key来避免幻读。
select * from t where a=1;属于快照读
select * from t where a=1 lock in share mode;属于当前读

不能把快照读和当前读得到的结果不一样这种情况认为是幻读,这是两种不同的使用。所以我认为mysql的rr级别是解决了幻读的。

MAGE001

MAGE001 commented on Jan 20, 2019

@MAGE001

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官方给出的幻读解释是:只要在一个事务中,第二次select多出了row就算幻读。
a事务先select,b事务insert确实会加一个gap锁,但是如果b事务commit,这个gap锁就会释放(释放后a事务可以随意dml操作),a事务再select出来的结果在MVCC下还和第一次select一样,接着a事务不加条件地update,这个update会作用在所有行上(包括b事务新加的),a事务再次select就会出现b事务中的新行,并且这个新行已经被update修改了,实测在RR级别下确实如此。

如果这样理解的话,Mysql的RR级别确实防不住幻读

多读出的一行,是因为 "MVCC快照读中,“自己的修改可见”。"还是update的锁把快照读变成了当前读。

ghost

ghost commented on Feb 16, 2019

@ghost

同意 @nvidi 的说法。楼上有几个例子中的两次查询根本就不一样的啊。

dunixd

dunixd commented on Mar 25, 2019

@dunixd

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官方给出的幻读解释是:只要在一个事务中,第二次select多出了row就算幻读。
a事务先select,b事务insert确实会加一个gap锁,但是如果b事务commit,这个gap锁就会释放(释放后a事务可以随意dml操作),a事务再select出来的结果在MVCC下还和第一次select一样,接着a事务不加条件地update,这个update会作用在所有行上(包括b事务新加的),a事务再次select就会出现b事务中的新行,并且这个新行已经被update修改了,实测在RR级别下确实如此。
如果这样理解的话,Mysql的RR级别确实防不住幻读

多读出的一行,是因为 "MVCC快照读中,“自己的修改可见”。"还是update的锁把快照读变成了当前读。

MVCC快照读本身就包括两部分可见:1、事务开始前已提交的可见 2、自己本事务的修改可见
因此这里应该是本身快照读不可见的记录,由于自己修改过变成了快照读可见

kcruci

kcruci commented on Mar 26, 2019

@kcruci

幻读包括count行数这种吗?

31 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @kcruci@kylexlau@lc87624@hzj629206@MAGE001

        Issue actions

          Innodb 中 RR 隔离级别能否防止幻读? · Issue #42 · Yhzhtk/note