一卓的博客

怕什么真理无穷,
进一寸有一寸的欢喜。

0%

InnoDB 的 MVCC 多版本并发控制

MVCC(Mutil-Version Concurrency Control),就是多版本并发控制。

MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。

在 MySQL 的 InnoDB 引擎中就是指在已提交读 (READ COMMITTD) 和可重复读 (REPEATABLE READ) 这两种隔离级别下的事务对于 SELECT 操作会访问版本链中的记录的过程。

这就使得别的事务可以修改这条记录,反正每次修改都会在版本链中记录。SELECT 可以去版本链中拿记录,这就实现了读-写,写-读的并发执行,提升了系统的性能。

InnoDB 向存储在数据库中的每一行添加三个字段:

  • 6 字节的 DB_TRX_ID 字段指示插入或更新该行的最后一笔事务的事务 ID。此外,删除在内部被视为更新,在该更新中,行中的特殊位被设置为将其标记为已删除。
  • 一个 7 字节的 DB_ROLL_PTR 字段称为滚动指针。回滚指针指向写入回滚段的撤消日志(undo log)记录。如果该行已更新,则撤消日志记录将包含在更新该行之前重建该行的内容所必需的信息。(注意插入操作的 undo 日志没有这个属性,因为它没有老版本)
  • 一个 6 字节的 DB_ROW_ID 字段包含一个行 ID,该行 ID 随着插入新行而单调增加。如果 InnoDB自动生成聚簇索引,则该索引包含行 ID 值。否则,该 DB_ROW_ID 列不会出现在任何索引中。

新增一个事务时事务 id 会增加,DB_TRX_ID 能够表示事务开始的先后顺序。

MVCC 优势

MVCC最大的优势:读不加锁,读写不冲突。在读多写少的 OLTP 应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能

缺点

每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。

数据结构

在分析MVCC原理之前,先看下 InnoDB 中数据行的结构:

img

在 InnoDB 中,每一行都有2个隐藏列 DB_TRX_ID 和DATA_ROLL_PTR(如果没有定义主键,则还有个隐藏主键列):

  1. DATA_TRX_ID表示最近修改该行数据的事务ID
  2. DATA_ROLL_PTR则表示指向该行回滚段的指针,该行上所有旧的版本,在undo中都通过链表的形式组织,而该值,正式指向undo中该行的历史记录链表

整个MVCC的关键就是通过DATA_TRX_ID和DATA_ROLL_PTR这两个隐藏列来实现的。

ReadView

  1. ReadView 说白了就是一个数据结构,在 SQL 开始的时候被创建。这个数据结构中包含了 3 个主要的成员:ReadView{low_trx_id, up_trx_id, trx_ids},在并发情况下,一个事务在启动时,trx_sys 链表中存在部分还未提交的事务,那么哪些改变对当前事务是可见的,哪些又是不可见的,这个需要通过 ReadView 来进行判定,首先来看下 ReadView 中的 3 个成员各自代表的意思:

    • low_trx_id 表示该 SQL 启动时,当前事务链表中最大的事务 id 编号,也就是最近创建的除自身以外最大事务编号;

    • up_trx_id 表示该 SQL 启动时,当前事务链表中最小的事务 id 编号,也就是当前系统中创建最早但还未提交的事务;

    • trx_ids 表示所有事务链表中事务的 id 集合。

上述3个成员组成了ReadView中的主要部分,简单图示如下:

img

根据上图所示,所有数据行上DATA_TRX_ID小于up_trx_id的记录,说明修改该行的事务在当前事务开启之前都已经提交完成,所以对当前事务来说,都是可见的。而对于DATA_TRX_ID大于low_trx_id的记录,说明修改该行记录的事务在当前事务之后,所以对于当前事务来说是不可见的。

注意,ReadView是与SQL绑定的,而并不是事务,所以即使在同一个事务中,每次SQL启动时构造的ReadView的up_trx_id和low_trx_id也都是不一样的,至于DATA_TRX_ID大于low_trx_id本身出现也只有当多个SQL并发的时候,在一个SQL构造完ReadView之后,另外一个SQL修改了数据后又进行了提交,对于这种情况,数据其实是不可见的。

最后,至于位于(up_trx_id, low_trx_id)中间的事务是否可见,这个需要根据不同的事务隔离级别来确定。对于RC的事务隔离级别来说,对于事务执行过程中,已经提交的事务的数据,对当前事务是可见的,也就是说上述图中,当前事务运行过程中,trx1~4中任意一个事务提交,对当前事务来说都是可见的;而对于RR隔离级别来说,事务启动时,已经开始的事务链表中的事务的所有修改都是不可见的,所以在RR级别下,low_trx_id基本保持与up_trx_id相同的值即可。

最后用一张图来解释MySQL中的MVCC实现:

img

参考链接

请作者喝杯咖啡吧