MySQL可重复读是否可解决幻读的问题?

195 阅读2分钟

MySQL存储引擎InnoDB的默认数据库隔离级别是:可重复读。

而可重复读可以很大程度避免幻读,并不是完全解决。

1. 什么是幻读?

翻阅MySQL官方手册(5.7版本),会发现有如下定义:

A row that appears in the result set of a query, but not in the result set of an earlier query. For example, if a query is run twice within a transaction, and in the meantime, another transaction commits after inserting a new row or updating a row so that it matches the WHERE clause of the query.

This occurrence is known as a phantom read. It is harder to guard against than a non-repeatable read, because locking all the rows from the first query result set does not prevent the changes that cause the phantom to appear.

Among different isolation levels, phantom reads are prevented by the serializable read level, and allowed by the repeatable read, consistent read, and read uncommitted levels.

在一个事务中,两次相同条件的查询结果是不一致的。举个例子,同一条查询在一个事务中被执行两次,在这两次查询之间,另一个事务插入或更新了这一条查询where条件中的记录并提交了。

在不同的数据库隔离级别中,串行化可避免幻读,而在另外三个隔离级别中是允许的:可重复读、一致性读和未提交读。

2. 可重复读

The default isolation level for InnoDB. It prevents any rows that are queried from being changed by other transactions, thus blocking non-repeatable reads but not phantom reads. It uses a moderately strict locking strategy so that all queries within a transaction see data from the same snapshot, that is, the data as it was at the time the transaction started.

When a transaction with this isolation level performs UPDATE ... WHERE, DELETE ... WHERE, SELECT ... FOR UPDATE, and LOCK IN SHARE MODE operations, other transactions might have to wait.

可重复读可防止查询到的数据被其它事务修改,可以避免不可重复读但不能避免幻读。使用适当严格的锁策略来保证同一个事务中的所有查询结果来自同一个快照,这个快照在事务开启时生成。底层通过MVCC(Multi-Version Concurrency Control)来实现,生成快照其实就是ReadView的应用。

在这种隔离级别下,如果一个事务在执行update..where, Delete...where, select....for update操作时,其它事务必须得等待。

对于当前读(select....for update),使用Next-Key Locking, 通过锁定索引记录及其周围的间隙,防止其它事务在锁定期间修改或插入与当前事务相关的记录。

3. 实践

3.1 查看数据库的默认隔离级别:

image.png

3.2 开启事务A:

image.png

查看目前数据库中的数据:

image.png

查询c1列为5的数据,结果为空 image.png

3.3 开启事务B:

image.png

插入c1列为5的数据:

image.png

提交事务:

image.png

3.4 事务A修改c1列为5的数据:

image.png

3.5 事务A查询c1列为5的数据:

image.png

这里跟第一次查询语句返回的结果不一致,发生了幻读的情况。

4. 参考手册

  1. MySQL 5.7 Reference Manual