InnoDB 幻读简述
Contents
什么是幻读
幻读,即某个事务在执行两次相同的 select 操作中,另一个事务插入了一条数据,导致两次 select 读取出的数据不同,会多出另一个事务插入的数据,违反了事务的隔离性。
举个例子,下图是两个事务,第 3、5 步执行了相同的 sql 查询语句,但是后一次查询比前一次查询,多出了 age = 10
的这一条数据。
MySQL 的 InnoDB 存储引擎在 RR 隔离级别就避免了幻读。
相关基本概念
快照读
通过 MVCC 的方式来读取数据库中的数据。如果读取的行正在进行修改操作,则会读取该行的快照数据。
在 RR 隔离级别下,会读取当前事务对应的行数据版本;而在 RC 隔离级别下,则会读取最新的行快照数据。
例如,普通的 select 就是快照读。
select * from t where ?;
当前读
在某些情况下,我们需要对行数据加锁,以保证数据的一致性。读取的行加锁后,其他事务修改该行,会被阻塞。
对应的 SQL 语句如下:
select ··· for update;
select ··· lock in share mode;
MVCC
多版本并发控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。
对于每个事务,都有一个版本号。每开始一个新的业务,事务版本号会递增。
MVCC 在每行数据后面,新增了隐藏的两列,创建版本号、删除版本号。
对于 select、insert、delete、update 四种操作,MVCC 操作逻辑如下:
SELECT
InnoDB会根据以下条件检查每一行记录:
-
InnoDB只查找版本早于当前事务版本的数据行,这样可以确保事务读取的行要么是在开始事务之前已经存在的,要么是事务自身插入或者修改过的,在事务开始之后才插入的行,事务不会看到。
-
行的删除版本号要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行在事务开始之前未被删除,在事务开始之前就已经过期的数据行,该事务也不会看到。只有符合上述两个条件的才会被查询出来
INSERT
将当前系统版本号作为数据行快照的创建版本号。
DELETE
将当前系统版本号作为数据行快照的删除版本号。
UPDATE
将当前系统版本号作为更新前的数据行快照的删除版本号,并将当前系统版本号作为更新后的数据行快照的创建版本号。可以理解为先执行 DELETE 后执行 INSERT。
行锁
行锁有三种:
- Record Lock:单个行记录上的锁。
- Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。
- Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。
如何解决幻读问题
对于快照读
,即 select * from t where ?
类型的 SQL 语句,读取数据时会比较行数据的版本,根据 MVCC 机制返回数据。
对于当前读
,读取时会相应地加行锁,以保证读取的行数据是最新的,并且其他事物不能修改相应的行数据。
参考资料
《高性能 MySQL》
《MySQL 技术内幕-InnoDB 存储引擎》
Author jakseer
LastMod 2020-08-15