MySql-MVCC

MySql-MVCC

MVCC介绍

MySql在可重复度与读已提交事务隔离级别下实现了MVCC机制。

undo日志版本链和Read View

undo日志

undo日志就是回滚日志,当修改一行数据时,undo日志会记录该行数据原始数据,当业务失败时,就根据undo日志的数据进行回滚事务。

undo日志版本链

对每一行数据进行多次修改时,undo日志会记录每一次操作执行后的数据,并将它们串联起来,不管这些操作是在同一个事务还是跨事务。

MySql会为每次对数据记录的修改操作添加两个隐藏字段:

  • trx_id(事务ID):当前操作发生所在的事务
  • roll_pointer(回滚指针):回滚到上一个操作的连接点

MySql使用roll_pointer将对数据修改的每个操作连接起来保存在数据库中。

一致性视图Read-View

当事务开启时,执行任何查询InnoDB表的语句都会生成当前事务的一致性视图Read-View,Read-View由两部分组成:

  • 执行查询时所有未提交的事务id数组(最小事务id:min_trxId)
  • 已创建的最大事务id(max_trxId)

事务中的任何查询sql都需要从对应日志版本链中最新数据开始,和Read View逐条对比,得到最终的快照结果

在RR和RC不同隔离级别中Read View的生成有不同表现:

  • 在RR中,开启事务后,执行第一次select查询InnoDB的表时生成Read View视图,Read View视图在该事务结束之前都不会变化,除非在事务中对该数据进行了修改,之后的查询会重新生成Read View视图;
  • 在RC中,每次执行查询语句时都会重新生成Read View视图;

Read View在每个事务中保存一份。

事务id

MySql在start transaction时并不会直接生成事务id,而是在执行第一条修改InnoDB表的语句时事务才真正启动,去申请事务id,MySql严格按照这个真正启动顺序分配事务id(依次递增)。

事务划分

MySql根据Read View中所有未提交的事务id数组(最小事务id:min_trxId)和最大事务id(max_trxId)把所有事务划分成三部分:

  • 已提交事务:事务id < min_trxId

  • 已提交和未提交事务:min_trxId <= 事务id <= max_trxId

    也就是说这里有未提交的事务,也包含已提交的事务。

  • 未开始事务:事务id > max_trxId

版本链对比规则

  • row的trx_id < min_trxId,这个版本的数据是由已提交的事务生成,数据是可见的;
  • row的trx_id > max_trxId,这个版本的数据由将来启动的事务生成的,数据不可见;
  • row的min_trxId <= trx_id <= max_trxId,有两种情况:
    • trx_id在Read View视图数组中,这个版本的数据是由还未提交的事务生成的,不可见,如果是在当前自己的事务,是可见的;
    • trx_id不在Read View视图数组中,这个版本的数据是已经提交的事务生成的,数据是可见的;

删除情况
delete可以看作是update的特殊情况,delete时步骤操作如下:

  1. 将版本链上最新版本的数据复制一份,然后将trx_id修改为删除操作的trx_id;
  2. 该条delete操作记录的头信息(Record Header)中的delete_flag标识位改为true,表示当前记录已经被删除;
  3. 查询时按照上面的版本链对比规则查到的记录如果delete_flag为true,则意味着记录已经被删除,不返回数据;

MVCC机制实现

MVCC就是通过一致性视图Read View和undo日志版本链进行对比,使不同事务读取数据时,读取该数据在undo日志版本链上的不同版本数据,来实现的。

MVCC示例

请看下面这个例子:
user表有三个字段,id(主键)、name和age,表数据如下:

id	name	age
1	小明	19
2	小张	19
3	小王	19
12	小红	19
35	小爱	19

接下来在不同事务中执行update和select语句,Sql执行时序图如下:
MySql-MVCC

这些update操作都会被记录在undo日志中,形式如下:
MySql-MVCC

select查询语句分析:

  1. 事务30中第7步的查询sql,已提交事务id=10,未提交事务id=20,最大的事务id=20,这时生成的Read View为:未提交的事务数组[20], 最大事务id=20,min_trxId = 20,max_trxId = 20:

    根据版本链对比规则当前row的trx_id = 10,而trx_id = 10 < 20,trx_id < min_trxId,事务trx_id=10的事务已经提交,数据可见,所以步骤7的查询语句的结果age=30;

  2. 事务30步骤9的查询,因为步骤7已经生成了Read View,所以步骤9的Read View也是:未提交的事务数组[20], 最大事务id=20:

    根据规则对比当前row的trx_id=20,20在Read View数组中,这个版本的数据是未提交的,数据是不可见的;再往下找,下一个row的trx_id = 10,而10 < min_trxId = 20,所以trx_id = 10的事务已经提交,数据可见,所以步骤9的查询结果age=30;

  3. 临时事务中步骤12的查询语句,已提交的事务有trx_id = 10和trx_id = 20,当前没有未提交的事务,最大事务id=20,这时的Read View为:未提交的事务数组[],最大事务id=20,max_trxId = 20:

    当前row的trx_id=20,根据版本链对比规则,虽然trx_id=20 <= max_trxId,但trx_id=20不在视图数组中,当前row的trx_id=20的事务已经提交了,数据是可见的,所以步骤12的查询结果age=40;

  4. 再回来查看事务30中步骤14的查询语句,因为事务30中执行了步骤13,对同一条数据进行了修改(id都是1),在修改的时候会在当时重新生成Read View,这时候已提交的事务有trx_id = 10和trx_id = 20,未提交的事务trx_id=30,最大事务id=30,所以Read View为:未提交的事务数组[30],最大事务id=30,min_trxId = 30, max_trxId = 30:

    当前row的trx_id = 30,根据版本链对比规则,trx_id <= max_trxId,并且trx_id=30在未提交的事务数组中,所以当前row的trx_id=30的事务未提交,数据不可见;再往下找,下一个row的trx_id=20,而trx_id=20 < min_trxId=30,所以当前row的trx_id=20的事务已经提交,数据是可见的,所以步骤14的查询结果age=40;

上一篇:ecmall数据库基本操作


下一篇:react源码解析8.render阶段