MySQL InnoDB MVCC深度分析
关于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)实现简要分析(水平很高,分析深入,必须要看,但可能不太好理解)
MySQL InnoDB MVCC深度分析的更多相关文章
- MySQL InnoDB MVCC
MySQL 原理篇 MySQL 索引机制 MySQL 体系结构及存储引擎 MySQL 语句执行过程详解 MySQL 执行计划详解 MySQL InnoDB 缓冲池 MySQL InnoDB 事务 My ...
- 何登成大神对Innodb加锁的分析
背景 MySQL/InnoDB的加锁分析,一直是一个比较困难的话题.我在工作过程中,经常会有同事咨询这方面的问题.同时,微博上也经常会收到MySQL锁相关的私信,让我帮助解决一些死锁的问题.本文,准备 ...
- MySQL InnoDB 实现高并发原理
MySQL 原理篇 MySQL 索引机制 MySQL 体系结构及存储引擎 MySQL 语句执行过程详解 MySQL 执行计划详解 MySQL InnoDB 缓冲池 MySQL InnoDB 事务 My ...
- (转)mysql、innodb和加锁分析
mysql.innodb和加锁分析 原文:https://liuzhengyang.github.io/2016/09/25/mysqlinnodb/ 介绍 本文主要介绍MySQL和InnoDB存储引 ...
- 定点分析: MySQL InnoDB是如何保证系统异常断电情况下的数据可靠性?
MySQL支持事务,所以保证数据可靠的前提是对数据的修改事务已经成功提交 这个问题可以解释为'MySQL InnoDB是如何保证事务C(一致性)D(持久性)性的?' 可能出现的两种情况: (一致性)数 ...
- mysql innodb锁简析(2)
继续昨天的innodb锁的分析: 注:此博文参考一下地址,那里讲的也很详细.http://xm-king.iteye.com/blog/770721 mysql事务的隔离级别分为四种,隔离级别越高,数 ...
- MySQL InnoDB存储引擎中的锁机制
1.隔离级别 Read Uncommited(RU):这种隔离级别下,事务间完全不隔离,会产生脏读,可以读取未提交的记录,实际情况下不会使用. Read Committed (RC):仅能读取到已提交 ...
- 腾讯云数据库团队:浅谈如何对MySQL内核进行深度优化
作者介绍:简怀兵,腾讯云数据库团队高级工程师,负责腾讯云CDB内核及基础设施建设:先后供职于Thomson Reuters和YY等公司,PTimeDB作者,曾获一项发明专利:从事MySQL内核开发工作 ...
- 搞懂MySQL InnoDB事务ACID实现原理
前言 说到数据库事务,想到的就是要么都做修改,要么都不做.或者是ACID的概念.其实事务的本质就是锁和并发和重做日志的结合体.那么,这一篇主要讲一下InnoDB中的事务到底是如何实现ACID的. 原子 ...
随机推荐
- Python创建目录文件夹
Python对文件的操作还算是方便的,只需要包含os模块进来,使用相关函数即可实现目录的创建. 主要涉及到三个函数 1.os.path.exists(path) 判断一个目录是否存在 2.os.mak ...
- [Leetcode] Remove duplicates from sorted array 从已排序的数组中删除重复元素
Given a sorted array, remove the duplicates in place such that each element appear only once and ret ...
- bzoj 2304 [Apio2011]寻路 Dij+模拟+恶心建图
[Apio2011]寻路 Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 624 Solved: 193[Submit][Status][Discus ...
- bzoj 3771 Triple FFT 生成函数+容斥
Triple Time Limit: 20 Sec Memory Limit: 64 MBSubmit: 847 Solved: 482[Submit][Status][Discuss] Desc ...
- JS知识总结
1.javascript继承机制 原型继承,访问对象属性时,如果对象内部有就返回,找不到就会从对象原型指向的对象原型中查找,一层一层的查找,直到最顶层的对象原型还找不到,就返回undefined. 2 ...
- 金中欢乐赛 C题
题目传送门 这道题 hash就可以写了 弄了半天有点智障 强行压一压就okay了的说 #include<cstdio> #include<cstring> #include&l ...
- poj 1528 Perfection
题目链接:http://poj.org/problem?id=1528 题目大意:输入一个数n,然后求出约数的和sum,在与这一个数n进行比较,如果sum>n,则输出ABUNDANT,如果sum ...
- webdriver函数
import sys; print('%s %s' % (sys.executable or sys.platform, sys.version)) PyDev console: starting. ...
- TCP的三次握手和四次挥手+TCP和UDP的区别
TCP的三次握手: LISTEN:表示服务器端的某个socket处于监听状态,可以接收连接了. SYN_SENT:当客户端SOCKET执行connect连接时,它首先发送syn报文,随即会进入到此状态 ...
- 【转】JSP自定义标签
转载自:http://www.cnblogs.com/edwardlauxh/archive/2010/05/20/1918587.html tld标签的描述文件 标签的描述文件是一个描述整个标签库标 ...