背景:php
以前面试被问到这么一个问题,数据库两个transaction,当transaction1在update某一行的时候,transaction2在select的时候会不会block。我之前用MySQL作过测试,印象是能够,可是面试官提出质疑,今天我用MySQL验证这个问题的仔细研究了一下MySQL的后台实现,后来再网上发现了下面这篇文章很是就转过来,不过文中有些地方逻辑上好像不太对,我没有时间去读MySQL源代码,就根据实际结果给出本身的推测,若是你们有明确答案请共享之。mysql
我先把文中感受不对的地方我本身总结的放出来(标红的为个人猜想),你们能够先看正文,而后再回头看个人总结:面试
- READ_UNCOMMITTED
读未提交时,读事务直接读取主记录,不管更新事务是否完成
- READ_COMMITTED
读提交时,读事务每次检查主记录上有没有锁,若是没有锁就读取主记录;若是有锁,就读取undo log中最近的版本。这样每次读到的都是最新COMMITTED的数据。所以两次对同一字段的读可能读到不一样的数据(幻读),但能保证每次都读到最新的数据。
- REPEATABLE_READ
第一次读的时候检查主记录上有没有锁,若是没有锁就读取主记录;若是有锁,就读取undo log中最近的版本。我猜想update的时候建立新的记录,而后将原先主记录内容拷贝到当前主记录中,原先的主记录就变为最新的undo log。之后每次都读取第一次读的版本,这样保证不会产生幻读,但可能读不到最新的数据
- SERIALIZABLE
锁表,读写相互阻塞,使用较少
正文连接以下:sql
http://blog.csdn.net/chen77716/article/details/6742128
数据库
Mysql究竟是怎么实现MVCC的?这个问题无数人都在问,但google中并没有答案,本文尝试从Mysql源码中寻找答案。并发
在Mysql中MVCC是在Innodb存储引擎中获得支持的,Innodb为每行记录都实现了三个隐藏字段:分布式
- 6字节的事务ID(
DB_TRX_ID
)
- 7字节的回滚指针(DB_ROLL_PTR)
- 隐藏的ID
6字节的事物ID用来标识该行所述的事务,7字节的回滚指针须要了解下Innodb的事务模型。
1. Innodb的事务相关概念
为了支持事务,Innbodb引入了下面几个概念:
- redo log
redo log就是保存执行的SQL语句到一个指定的Log文件,当Mysql执行recovery时从新执行redo log记录的SQL操做便可。当客户端执行每条SQL(更新语句)时,redo log会被首先写入log buffer;当客户端执行COMMIT命令时,log buffer中的内容会被视状况刷新到磁盘。redo log在磁盘上做为一个独立的文件存在,即Innodb的log文件。
- undo log
与redo log相反,undo log是为回滚而用,具体内容就是copy事务前的数据库内容(行)到undo buffer,在适合的时间把undo buffer中的内容刷新到磁盘。undo buffer与redo buffer同样,也是环形缓冲,但当缓冲满的时候,undo buffer中的内容会也会被刷新到磁盘;与redo log不一样的是,磁盘上不存在单独的undo log文件,全部的undo log均存放在主ibd数据文件中(表空间),即便客户端设置了每表一个数据文件也是如此。
- rollback segment
回滚段这个概念来自Oracle的事物模型,在Innodb中,undo log被划分为多个段,具体某行的undo log就保存在某个段中,称为回滚段。能够认为undo log和回滚段是同一意思。
- 锁
Innodb提供了基于行的锁,若是行的数量很是大,则在高并发下锁的数量也可能会比较大,据Innodb文档说,Innodb对锁进行了空间有效优化,即便并发量高也不会致使内存耗尽。
对行的锁有分两种:排他锁、共享锁。共享锁针对对,排他锁针对写,彻底等同读写锁的概念。若是某个事务在更新某行(排他锁),则其余事物不管是读仍是写本行都必须等待;若是某个事物读某行(共享锁),则其余读的事物无需等待,而写事物则需等待。经过共享锁,保证了多读之间的无等待性,可是锁的应用又依赖Mysql的事务隔离级别。
- 隔离级别
隔离级别用来限制事务直接的交互程度,目前有几个工业标准:
- READ_UNCOMMITTED:脏读
- READ_COMMITTED:读提交
- REPEATABLE_READ:重复读
- SERIALIZABLE:串行化
Innodb对四种类型都支持,脏读和串行化应用场景很少,读提交、重复读用的比较普遍,后面会介绍其实现方式。
2. 行的更新过程
下面演示下事务对某行记录的更新过程:
1. 初始数据行
F1~F6是某行列的名字,1~6是其对应的数据。后面三个隐含字段分别对应该行的事务号和回滚指针,假如这条数据是刚INSERT的,能够认为ID为1,其余两个字段为空。
2.事务1更改该行的各字段的值
当事务1更改该行的值时,会进行以下操做:
- 用排他锁锁定该行
- 记录redo log
- 把该行修改前的值Copy到undo log,即上图中下面的行
- 修改当前行的值,填写事务编号,使回滚指针指向undo log中的修改前的行
3.事务2修改该行的值
与事务1相同,此时undo log,中有有两行记录,而且经过回滚指针连在一块儿。
所以,若是undo log一直不删除,则会经过当前记录的回滚指针回溯到该行建立时的初始内容,所幸的时在Innodb中存在purge线程,它会查询那些比如今最老的活动事务还早的undo log,并删除它们,从而保证undo log文件不至于无限增加。
4. 事务提交
当事务正常提交时Innbod只须要更改事务状态为COMMIT便可,不需作其余额外的工做,而Rollback则稍微复杂点,须要根据当前回滚指针从undo log中找出事务修改前的版本,并恢复。若是事务影响的行很是多,回滚则可能会变的效率不高,根据经验值没事务行数在1000~10000之间,Innodb效率仍是很是高的。很显然,Innodb是一个COMMIT效率比Rollback高的存储引擎。听说,Postgress的实现刚好与此相反。
5. Insert Undo log
上述过程确切地说是描述了UPDATE的事务过程,其实undo log分insert和update undo log,由于insert时,原始的数据并不存在,因此回滚时把insert undo log丢弃便可,而update undo log则必须遵照上述过程。
3. 事务级别
众所周知地是更新(update、insert、delete)是一个事务过程,在Innodb中,查询也是一个事务,只读事务。当读写事务并发访问同一行数据时,能读到什么样的内容则依赖事务级别:
- READ_UNCOMMITTED
读未提交时,读事务直接读取主记录,不管更新事务是否完成
- READ_COMMITTED
读提交时,读事务每次都读取undo log中最近的版本,所以两次对同一字段的读可能读到不一样的数据(幻读),但能保证每次都读到最新的数据。
- REPEATABLE_READ
每次都读取指定的版本,这样保证不会产生幻读,但可能读不到最新的数据
- SERIALIZABLE
锁表,读写相互阻塞,使用较少
读事务通常有SELECT语句触发,在Innodb中保证其非阻塞,但带FOR UPDATE的SELECT除外,带FOR UPDATE的SELECT会对行加排他锁,等待更新事务完成后读取其最新内容。就整个Innodb的设计目标来讲,就是提供高效的、非阻塞的查询操做。
4. MVCC
上述更新前创建undo log,根据各类策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,这个可能与咱们所理解的MVCC有较大的出入,通常咱们认为MVCC有下面几个特色:
- 每行数据都存在一个版本,每次数据更新时都更新该版本
- 修改时Copy出当前版本随意修改,个事务之间无干扰
- 保存时比较版本号,若是成功(commit),则覆盖原记录;失败则放弃copy(rollback)
就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道。。。,而Innodb的实现方式是:
- 事务以排他锁的形式修改原始数据
- 把修改前的数据存放于undo log,经过回滚指针与主数据关联
- 修改为功(commit)啥都不作,失败则恢复undo log中的数据(rollback)
两者最本质的区别是,当修改数据时是否要排他锁定,若是锁定了还算不算是MVCC?
Innodb的实现真算不上MVCC,由于并无实现核心的多版本共存,undo log中的内容只是串行化的结果,记录了多个事务的过程,不属于多版本共存。但理想的MVCC是难以实现的,当事务仅修改一行记录使用理想的MVCC模式是没有问题的,能够经过比较版本号进行回滚;但当事务影响到多行数据时,理想的MVCC据无能为力了。
好比,若是Transaciton1执行理想的MVCC,修改Row1成功,而修改Row2失败,此时须要回滚Row1,但由于Row1没有被锁定,其数据可能又被Transaction2所修改,若是此时回滚Row1的内容,则会破坏Transaction2的修改结果,致使Transaction2违反ACID。
理想MVCC难以实现的根本缘由在于企图经过乐观锁代替二段提交。修改两行数据,但为了保证其一致性,与修改两个分布式系统中的数据并没有区别,而二提交是目前这种场景保证一致性的惟一手段。二段提交的本质是锁定,乐观锁的本质是消除锁定,两者矛盾,故理想的MVCC难以真正在实际中被应用,Innodb只是借了MVCC这个名字,提供了读的非阻塞而已。
5.总结
也不是说MVCC就无处可用,对一些一致性要求不高的场景和对单一数据的操做的场景仍是能够发挥做用的,好比多个事务同时更改用户在线数,若是某个事务更新失败则从新计算后重试,直至成功。这样使用MVCC会极大地提升并发数,并消除线程锁。
6. 参考资料