关于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. [OS] 进程相关知识点

    进程概念: 1.程序在执行中 2.一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程,是系统进行资源分配和调度的独立单位. 进程与程序的差别: ·进程----动态, 程序----静态 ·进程 ...

  2. NET 的 ELK 监控方案

    NET 的 ELK 监控方案 https://www.jianshu.com/p/3c26695cfc38 背景就不多说了,谁家没有个几个十系统在跑啊.如何监控这几十个系统的运行状况,对于非运营人员来 ...

  3. 【bzoj3813】奇数国 线段树

    题目描述 给出一个长度为n的序列,每个数都可以由前60个质数的乘积表示,初始每个数都是3.支持两种操作:(1)修改一个数 (2)查询一段区间内所有数的乘积的欧拉函数值模19961993. 输入 第一行 ...

  4. [bzoj4832][Lydsy1704月赛]抵制克苏恩

    题目大意:有一个英雄和若干个所从,克苏恩会攻击$K$次,每次回随机攻击对方的一个人,造成$1$的伤害.现在对方有一名克苏恩,你有一些随从.如果克苏恩攻击了你的一名随从,若这名随从不死且你的随从数量不到 ...

  5. CF451E Devu and Flowers 解题报告

    CF451E Devu and Flowers 题意: \(Devu\)有\(N\)个盒子,第\(i\)个盒子中有\(c_i\)枝花.同一个盒子内的花颜色相同,不同盒子的花颜色不同.\(Devu\)要 ...

  6. HAOI2006 均分数据 [模拟退火]

    题目描述 已知N个正整数:A1.A2.--.An .今要将它们分成M组,使得各组数据的数值和最平均,即各组的均方差最小.均方差公式如下: 输入输出格式 输入格式: 输入文件data.in包括: 第一行 ...

  7. Codeforces Round #525 (Div. 2)D. Ehab and another another xor problem

    D. Ehab and another another xor problem 题目链接:https://codeforces.com/contest/1088/problem/D Descripti ...

  8. P2764 最小路径覆盖问题

    题目描述 «问题描述: 给定有向图G=(V,E).设P 是G 的一个简单路(顶点不相交)的集合.如果V 中每个顶点恰好在P 的一条路上,则称P是G 的一个路径覆盖.P 中路径可以从V 的任何一个顶点开 ...

  9. ES6学习笔记(五)—— 编程风格

    1. 块级作用域 let 取代 var —— let 只在声明的代码块内有效,而且不存在变量提升的效用 const 取代 let —— const 比较符合函数式编程的思想,运算不改变值,只是新建值: ...

  10. svn“Previous operation has not finished; run 'cleanup' if it was interrupted“ 或者不能cleanup,或者提示空目录 报错的解决方法

    参考了文档: http://blog.csdn.net/superch0054/article/details/38668017 今天碰到了个郁闷的问题,svn执行clean up命令时报错“Prev ...