要解决读一致性的问题,保证一个事务中前后两次读取数据结果一致,还有一种 MVCC 的方式,又叫多版本的并发控制(Multi Version Concurrency Control)。

MVCC 就是为了让一个事务前后两次读取到的数据保持一致,在修改数据的时候给它建立一个快照,后面的查询操作读取这个快照就可以了。MVCC 解决了加上排他锁后的行记录,可以被查询到的问题,从而解决了由于写操作阻塞而引发读操作并发的问题。

InnoDB 为每一行记录都默认生成两个隐藏列:DATA_TRX_ID 、 DATA_ROLL_PTR 。

DATA_TRX_ID,记录最新插入或者更新这条行记录的事务 ID ,大小为 6 个字节,事务编号是自动递增的(我们把它理解为创建版本号,在数据新增或者修改为新数据的时候,记录当前事务 ID)。

DATA_ROLL_PTR,表示指向该行回滚段 (rollback segment) 的指针,大小为 7 个字节,InnoDB 便是通过这个指针找到之前版本的数据,我们把它理解为回滚版本号。该行记录的所有旧版本,在 undo 中都通过链表的形式组织。

1

MVCC 的原理讲解

深入揭秘:MySQL中MVCC的神奇原理_版本号

第一个事务:

begin; 
insert into person values(1,'张三'); 
insert into person values(2,'李四'); 
commit;
  • 1.
  • 2.
  • 3.
  • 4.

此时的数据,创建版本是当前事务ID,删除版本为空,如下表:

深入揭秘:MySQL中MVCC的神奇原理_回滚_02

第二个事务:执行第 1 次查询,读取到两条原始数据,是“张三”和“李四”两个数据,这个时候事务 ID 是 2,

select * from person;
  • 1.

第三个事务,插入数据:

begin; 
insert into person values(3,'王五'); 
commit;
  • 1.
  • 2.
  • 3.

此时的数据,多了一条“王五”,它的创建版本号是当前事务编号 3,如下表:

深入揭秘:MySQL中MVCC的神奇原理_数据库_03

第二个事务,执行第 2 次查询:

select * from person;
  • 1.

MVCC 的查找规则:只能查找创建时间小于等于当前事务 ID 的行,和删除时间大于当前事务 ID 的行(或者回滚版本为空的行)。

因此以上的过程不能查到第三个事务插入的数据“王五”,“王五”的创建版本号为 3 大于第二个事务的 ID 2,所以还是只能查到创建版本号为 1 的两条数据,即“张三”和“李四”两个数据。

第四个事务,删除数据,删除了 id = 2 的“李四”的这条记录,

begin;
delete from mvcctest where id = 2;
commit;
  • 1.
  • 2.
  • 3.

 此时的数据,“李四”的回滚版本号被记录为当前事务 ID 4,其他数据不变,如下表:

深入揭秘:MySQL中MVCC的神奇原理_版本号_04

在第二个事务中,执行第 3 次查询:

select * from person;
  • 1.

MVCC 的查找规则:只能查找创建时间小于等于当前事务 ID 的行,和删除时间大于当前事务 ID 的行(或者回滚版本为空的行)。

因此以上的过程能查到第四个事务删除的数据“王五”,“王五”的回滚版本号 为 4 大于第二个事务的 ID 2,所以还是只能查到创建版本号为 1 的数据和回滚版本号为 4 的数据,还是“张三”和“李四”两个数据。

第五个事务,执行更新操作,这个事务 ID是5,

begin; 
update person set name = '赵六' where id = 1;
commit;
  • 1.
  • 2.
  • 3.

此时的数据,第五个事务更新“张三”数据的时候,旧数据的回滚版本号被记录为当前事务ID 5(同时写入 undo 日志),产生了一条新数据,创建版本号为当前事务ID 5,如下表:

深入揭秘:MySQL中MVCC的神奇原理_mysql_05

第二个事务,执行第 4 次查询,

select * from person;
  • 1.

MVCC 的查找规则:只能查找创建时间小于等于当前事务 ID 的行,和删除时间大于当前事务 ID 的行(或者回滚版本为空的行)。

因此以上的过程不能查到第三个事务插入的数据“王五”,“王五”的创建版本号为 3 大于第二个事务的 ID 2,所以还是只能查到创建版本号为 1 的两条数据,即“张三”和“李四”两个数据。

第五个事务更新后的数据"赵六"的创建版本号大于 2,代表是在第二个事务之后增加的,查不出来,而旧数据“张三”的回滚版本号大于 2,代表是在第二个事务之后删除的,可以查出来。所以还是只能查到创建版本号为 1 的两条数据,即“张三”和“李四”两个数据。

通过以上 MVCC 的讲解,我们能看到,通过创建版本号与回滚版本号的查找规则,无论其他事务是插入、修改、或者删除,第二个事务查询到的数据都没有变化,从而说明 MVCC 解决了读一致性的问题。

需要说明的是,以上的例子是基于第二个事务是查询操作,如果查询操作在插入、修改、或者删除之后,同时这些操作没有提交的情况下,那么按照 MVCC 的查找规则,查询的事务还是不能保证的一致性。

在 InnoDB 存储引擎中,MVCC 是通过 MVCC 的查找规则 结合  Undo Log 一起实现的读一致性。

2

理解 Undo Log、Redo Log 的作用

深入揭秘:MySQL中MVCC的神奇原理_数据库_06

1、了解 Undo Log

Undo Log 是为了实现事务的原子性产生的,在事务开始之前,在对数据进行操作之前,首先需要把操作的数据备份到一个日志文件里,以便在事务处理过程中出现异常或者回退时,MySQL 可以利用 Undo Log 中的备份数据恢复到事务执行前的状态,主要是用于回滚操作。

上面提到的 MVCC 是通过 MVCC 的查找规则 结合  Undo Log 一起实现的读一致性的根本原因在于 Undo  Log 保存了未提交之前的老版本数据,因此可以将其作为老版本快照便于其他事务来进行并发的读操作。

快照读:普通的 select 查询就是快照读,它是由缓存区(Undo buffer)和 undo 区(Undo Log)两部分组成。

当前读:sql 读取的是最新版本的数据,加锁的数据读取都是当前读。

2、了解 Redo Log

Redo Log 是为了实现恢复操作产生的,是为了实现事务的持久性。Redo Log 把事务中操作的任何数据,都把最新的数据备份到一个日志文件里,它不是事务提交后才写入日志文件,而是在事务的执行过程中就开始写入 Redo Log。

Redo Log 实现事务的持久性是指在发生故障的时候,如果有脏页没有写入磁盘,在重启 MySQL 服务的时候,根据 Redo Log 进行重做,从而把没有写入磁盘数据进行持久化的操作。

深入揭秘:MySQL中MVCC的神奇原理_回滚_07

后面将为大家介绍 SQL 语句调优 explain 的用法。