关于MySQL的InnoDB的MVCC原理,很多朋友都能说个大概:

每行记录都含有两个隐藏列,分别是记录的创建时间与删除时间

每次开启事务都会产生一个全局自增ID

在RR隔离级别下

INSERT ->  记录的创建时间 = 当前事务ID,删除时间 = NULL

DELETE -> 记录的创建时间不动,删除时间 = 当前事务ID

UPDATE -> 将记录复制一次

        老记录的创建时间不动,删除时间 = 当前事务ID

        新记录的创建时间 = 当前事务ID,删除时间 = NULL

SELECT -> 返回的记录需要满足两个条件:

        创建时间 <= 当前事务ID (记录是在当前事务之前或者由当前事务创建的)

        删除时间 == NULL || 删除时间 > 当前事务ID (记录是在当前事务之后被删除的)

但实际上,这个描述是很不严格的,问题有以下几点:

1. 每条记录含有的隐藏列不是两个而是三个

它们分别是:

DB_TRX_ID, 6byte, 创建这条记录/最后一次更新这条记录的事务ID

DB_ROLL_PTR, 7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)

DB_ROW_ID, 6byte,隐含的自增ID,如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引

另外,每条记录的头信息(record header)里都有一个专门的bit(deleted_flag来表示当前记录是否已经被删除

2. 记录的历史版本是放在专门的rollback segment里(undo log)

  UPDATE非主键语句的效果是

    老记录被复制到rollback segment中形成undo log,DB_TRX_ID和DB_ROLL_PTR不动

    新记录的DB_TRX_ID = 当前事务ID,DB_ROLL_PTR指向老记录形成的undo log

    这样就能通过DB_ROLL_PTR找到这条记录的历史版本。如果对同一行记录执行连续的update操作,新记录与undo log会组成一个链表,遍历这个链表可以看到这条记录的变迁)

3. MySQL的一致性读,是通过一个叫做read view的结构来实现的

read_view中维护了系统中活跃事务集合的快照,这些活跃事务ID的最小值为up_limit_id,最大值为low_limit_id(不要搞反了!!!)

附上源码注释以便于理解

trx_id_t low_limit_id; // The read should not see any transaction with trx id >= this value. In other words, this is the "high water mark".
trx_id_t up_limit_id; // The read should see all trx ids which are strictly smaller (<) than this value. In other words, this is the "low water mark".

SELECT操作返回结果的可见性是由以下规则决定的:

DB_TRX_ID < up_limit_id  -> 此记录的最后一次修改在read_view创建之前,可见

DB_TRX_ID > low_limit_id   -> 此记录的最后一次修改在read_view创建之后,不可见  ->  需要用DB_ROLL_PTR查找undo log(此记录的上一次修改),然后根据undo log的DB_TRX_ID再计算一次可见性

up_limit_id <= DB_TRX_ID <= low_limit_id -> 需要进一步检查read_view中是否含有DB_TRX_ID

    DB_TRX_ID ∉ read_view  -> 此记录的最后一次修改在read_view创建之前,可见

    DB_TRX_ID ∈ read_view -> 此记录的最后一次修改在read_view创建时尚未保存,不可见  ->  需要用DB_ROLL_PTR查找undo log(此记录的上一次修改),然后根据undo log的DB_TRX_ID再从头计算一次可见性

经过上述规则的决议,我们得到了这条记录相对read_view来说,可见的结果。

此时,如果这条记录的delete_flag为true,说明这条记录已被删除,不返回。

   如果delete_flag为false,说明此记录可以安全返回给客户端

4. 用MVCC这一种手段可以同时实现RR与RC隔离级别

它们的不同之处在于:

RR:read view是在first touch read时创建的,也就是执行事务中的第一条SELECT语句的瞬间,后续所有的SELECT都是复用这个read view,所以能保证每次读取的一致性(可重复读的语义)

RC:每次读取,都会创建一个新的read view。这样就能读取到其他事务已经COMMIT的内容。

所以对于InnoDB来说,RR虽然比RC隔离级别高,但是开销反而相对少。

补充:RU的实现就简单多了,不使用read view,也不需要管什么DB_TRX_ID和DB_ROLL_PTR,直接读取最新的record即可。

5. 二级索引与MVCC

MySQL的索引分为聚簇索引(clustered index)与二级索引(secondary index)两种。

刚才讲的内容是基于聚簇索引的,只有聚簇索引中含有DB_TRX_ID与DB_ROLL_PTR隐藏列,可以比较容易的实现MVCC

但是二级索引中并不含有这几个隐藏列,只含有1个bit的deleted flag,咋办?

  好办,如果UPDATE语句涉及到二级索引的键值,将老的二级索引的deleted flag标记为true,然后创建一条新的二级索引记录即可。

但是如果想根据二级索引来做查询,这可就麻烦了。因为二级索引不维护版本信息,无法判断二级索引中记录的可见性。

所以还是需要回到聚簇索引中来:

根据二级索引维护的主键值去聚簇索引中查找记录(使用MVCC规则)

如果查出来的结果跟二级索引里维护的结果相同 -> 返回,如果不同 -> 丢弃

如果对于一条查询语句,二级索引中有很多条满足条件的结果(连续多次更新,导致二级索引中有很多条记录),那上面这个流程就比较低效了。所以InnoDB的作者搞了个机智的小优化:

在二级索引中,用一个额外的名为MAX_TRX_ID的变量来记录最后一次更新二级索引的事务的ID

那么,如果当前语句关联的read_view的 up_limit_id > MAX_TRX_ID,说明在创建read_view时最后一次更新二级索引的事务已经结束,也就是说二级索引里的所有记录对于当前查询都是可见的,此时可以直接根据二级索引的deleted flag来确定记录是否应该被返回。

小结一下:二级索引的MVCC可见性判断在MAX_TRX_ID失效的情况下需要依赖聚簇索引才能完成。

6. purge

从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。

为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。

为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view

如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。

参考文献

InnoDB多版本(MVCC)实现简要分析(水平很高,分析深入,必须要看,但可能不太好理解)

深入浅出INNODB MVCC机制与原理

MVCC原理探究及MySQL源码实现分析

关于InnoDB中mvcc和覆盖索引查询的困惑?

InnoDB Multi-Versioning

MySQL 一致性读 深入研究

MySQL InnoDB MVCC深度分析的更多相关文章

  1. MySQL InnoDB MVCC

    MySQL 原理篇 MySQL 索引机制 MySQL 体系结构及存储引擎 MySQL 语句执行过程详解 MySQL 执行计划详解 MySQL InnoDB 缓冲池 MySQL InnoDB 事务 My ...

  2. 何登成大神对Innodb加锁的分析

    背景 MySQL/InnoDB的加锁分析,一直是一个比较困难的话题.我在工作过程中,经常会有同事咨询这方面的问题.同时,微博上也经常会收到MySQL锁相关的私信,让我帮助解决一些死锁的问题.本文,准备 ...

  3. MySQL InnoDB 实现高并发原理

    MySQL 原理篇 MySQL 索引机制 MySQL 体系结构及存储引擎 MySQL 语句执行过程详解 MySQL 执行计划详解 MySQL InnoDB 缓冲池 MySQL InnoDB 事务 My ...

  4. (转)mysql、innodb和加锁分析

    mysql.innodb和加锁分析 原文:https://liuzhengyang.github.io/2016/09/25/mysqlinnodb/ 介绍 本文主要介绍MySQL和InnoDB存储引 ...

  5. 定点分析: MySQL InnoDB是如何保证系统异常断电情况下的数据可靠性?

    MySQL支持事务,所以保证数据可靠的前提是对数据的修改事务已经成功提交 这个问题可以解释为'MySQL InnoDB是如何保证事务C(一致性)D(持久性)性的?' 可能出现的两种情况: (一致性)数 ...

  6. mysql innodb锁简析(2)

    继续昨天的innodb锁的分析: 注:此博文参考一下地址,那里讲的也很详细.http://xm-king.iteye.com/blog/770721 mysql事务的隔离级别分为四种,隔离级别越高,数 ...

  7. MySQL InnoDB存储引擎中的锁机制

    1.隔离级别 Read Uncommited(RU):这种隔离级别下,事务间完全不隔离,会产生脏读,可以读取未提交的记录,实际情况下不会使用. Read Committed (RC):仅能读取到已提交 ...

  8. 腾讯云数据库团队:浅谈如何对MySQL内核进行深度优化

    作者介绍:简怀兵,腾讯云数据库团队高级工程师,负责腾讯云CDB内核及基础设施建设:先后供职于Thomson Reuters和YY等公司,PTimeDB作者,曾获一项发明专利:从事MySQL内核开发工作 ...

  9. 搞懂MySQL InnoDB事务ACID实现原理

    前言 说到数据库事务,想到的就是要么都做修改,要么都不做.或者是ACID的概念.其实事务的本质就是锁和并发和重做日志的结合体.那么,这一篇主要讲一下InnoDB中的事务到底是如何实现ACID的. 原子 ...

随机推荐

  1. 怎么获取textarea中选中文字

    textarea设置select="saveSelectionText()" //保存选中内容 saveSelectionText: function () { var focus ...

  2. 算法(5)Jump Game

    题目:非负数的数组,每个数组元素代表这你能最大跨越多少步,初始在0的位置,问,能不能正好调到数组的最后一位! https://leetcode.com/problems/jump-game/#/des ...

  3. 通过数据库评估存储设备IO性能-Oracle11gIO校准功能介绍

    通过数据库评估存储设备IO性能 ---------Oracle11g IO校准功能介绍 前言 I/O子系统是ORACLE数据库的一个重要组成部分,因为I/O操作是贯穿数据库管理全过程,它操作的对象包括 ...

  4. CSS兼容性总结

    一.针对IE6的 !important 必须写在前面,例如: background:#9C6 !important;background:#999; 二.CSS HACK //IE6 专用 _heig ...

  5. Java —— Reflect反射机制

    JAVA反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java的反射机制. ...

  6. Springboot2.0 集成shiro权限管理

    在springboot中结合shiro教程搭建权限管理,其中几个小细节的地方对新手不友好,伸手党更是无法直接运行代码,搭建过程容易遇坑,记录一下.关键的地方也给注释了. 版本:springboot版本 ...

  7. CMD批处理把txt文本中的每行写入一个新文件,第一列作文件名

    需求 现在有一个文件格式如图 ID 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17001 89.84 8.87 1.29 -0.0 0.0 68.99 0.0 0. ...

  8. D. Equalize the Remainders (set的基本操作)

    D. Equalize the Remainders time limit per test 3 seconds memory limit per test 256 megabytes input s ...

  9. dbcp重连问题排查

    转载自:http://lc87624.iteye.com/blog/1734089 使用数据库连接池时,免不了会遇到断网.数据库挂掉等异常状况,当网络或数据库恢复时,若无法恢复连接池中的连接,那必然会 ...

  10. 在Idea中使用Eclipse编译器

    Eclipse编译器对Javac编译器的优点如下: 1.Proceed on errors 如果使用Javac编译器,你除了在执行之前修复所有错误之外没有其它的选择.然而Eclipse编译器却可以不管 ...