MySQL InnoDB 的多版本并发控制(MVCC)
innoDB 的多版本并发控制(MVCC)
1. MVCC定义
1.1定义
MVCC全称Mutli Version Concurreny Control,多版本并发控制,也可称之为一致性非锁定读;它通过行的多版本控制方式来读取当前执行时间数据库中的行数据。实质上使用的是快照数据,这样就可以实现不加锁读。MVCC 主要应用于 Read Commited 和 Repeatable read 两个事务隔离级别。
1.2一些困惑
- MYSQL的事务隔离级中的RC,RR级别怎么实现?
- 事务回滚操作是怎么回滚?
- 数据多版本并发控制中的多版本在哪里?
2.innoDB 的逻辑存储结构
innoDB 的数据保存在表空间中,表空间又包含各种段,其中有数据段,索引段,回滚段。InnoDB中数据以B+Tree的数据结构存储的,非叶子节点既是索引,叶子节点既是数据行,回滚段用于存储undoLog,undoLog中记录的就是多版本数据,用于快照读和事务失败后的数据回滚,MySQL在合适的时机会清理undoLog。
960295-20180123215304569-1124033252.png
3.MVCC的实现
MVCC的实现依赖于 每行的隐藏字段,DB_TRX_ID
,DB_ROLL_PTR
,删除标记位,还有read_view。
3.1 三个隐藏字段
innoDB 向数据库中存储的每行添加三个隐藏字段(有的书上说是两个,但是我看官方文档说是三个)。
数据初始化.png1 DB_TRX_ID
事务id
占6 字节,表示这一行数据最后插入或修改的事务id。此外删除在内部也被当作一次更新,在行的特殊位置添加一个删除标记(记录头信息有一个字节存储是否删除的标记)。
2 DB_ROLL_PTR
回滚指针
占7字节,回滚指针指向被写在Rollback segment
中的undoLog记录,在该行数据被更新的时候,undoLog 会记录该行修改前内容到undoLog。
3 DB_ROW_ID
行ID
占7字节,他就项自增主键一样随着插入新数据自增。如果表中不存主键 或者 唯一索引,那么数据库 就会采用DB_ROW_ID
生成聚簇索引。否则DB_ROW_ID
不会出现在索引中。
3.2 undo log
undo log是为回滚而用,具体内容就是copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。undo buffer与redo buffer一样,也是环形缓冲,但当缓冲满的时候,undo buffer中的内容会也会被刷新到磁盘;与redo log不同的是,磁盘上不存在单独的undo log文件,所有的undo log均存放在主ibd数据文件中(表空间),即使客户端设置了每表一个数据文件也是如此。
undo log 在 Rollback segment
中又被细分为 insert 和 update undo log , insert 类型的undo log 仅仅用于事务回滚,当事务一旦提交,insert undo log 就会被丢弃。update的undo log 被用于 一致性的读和事务回滚,update undo log 的清理 是在 没有事务 需要对这部分数据快照进行一致性读的时候 进行清理。
undo log 的创建
每次对数据进行更新操作时,都会copy 当前数据,保存到undo log 中。并修改 当前行的 回滚指针指向 undo log 中的 旧数据行。
undolog.png
3.3 read_view 判断数据行可见性
在innodb中,创建一个新事务的时候,innodb会将当前系统中的活跃事务列表创建一个副本(read view),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。当用户在这个事务中要读取该行记录的时候,innodb会将该行当前的版本号与该read view进行比较。
具体的算法是(可重复读级别):
假设当前 数据行 事务ID 为 T0 ,read view 中保存的 最老的事务id T_min ,最新的 事务id 为 T_max,当前进行的事务id 为 T_new 。
-
如果 T0 < T_min ,那么该行数据可见。
因为 T0 在 T_new 事务开始前 已经提交。
-
如果 T0 > T_max ,数据行不可见。根据
DB_ROLL_PTR
指针 找到下一个 数据版本,再次进行数据可见性判断。因为 T0事务 在 T_new 开始前并不存在,也就是说T0 在T_new 开始后 创建。
-
如果 T_min <= T0 <= T_max ,判断T0 是否在read_view 中,如果 不在该行数据可见。如果不可见根据
DB_ROLL_PTR
指针 找到下一个 数据版本,再次进行数据可见性判断。
3.4 Read Commited ,Repeatable read 数据可见性判断
Read Commited 和 Repeatable read 采用相同的数据可见性判断逻辑。
那么怎么在相同的判断逻辑下 分别 实现 RC 和 RR 级别的?
- Read Commited
在每次语句执行的过程中,都关闭read_view, 重新创建当前的一份新的read_view。
这样就可以根据当前的全局事务链表创建read_view的事务区间,实现read committed隔离级别。 - Repeatable read
在repeatable read的隔离级别下,创建事务trx结构的时候,就生成了当前的global read view。
使用trx_assign_read_view函数创建,一直维持到事务结束,这样就实现了repeatable read隔离级别。
正是因为Read Commited和 Repeatable read的read view 生成方式和时机不同,导致在不同隔离级别下,read committed 总是读最新一份快照数据,而repeatable read 读事务开始时的行数据版本。
4. 新的疑问
每行的记录头信息保存在什么位置,官方文档说是删除是逻辑的删除,删除只是记录一个删除标记。
通过查看一些博客说是被记录在记录头信息中,但是官方说的三个隐藏字段并没有提到这个记录头信息。那他到底算不算隐藏字段。如果不是隐藏字段那他记录在哪里?
1253350-20180420172417526-524213118.jpg