Mysql到底是怎么实现MVCC的

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. 参考资料

from  http://blog.csdn.net/chen77716/article/details/6742128

时间: 2024-11-08 15:12:47

Mysql到底是怎么实现MVCC的的相关文章

MySQL到底能支持多大的数据量?

MySQL是中小型网站普遍使用的数据库之一,然而,很多人并不清楚MySQL到底能支持多大的数据量,再加上某些国内CMS厂商把数据承载量的责任推给它,导致很多不了解MySQL的站长对它产生了很多误解,那么,MySQL的数据量到底能支持多少呢?其实MySQL单表的上限,主要与操作系统支持的最大文件大小有关.我们来看一下官方的介绍. MySQL表最大能达到多少? MySQL 3.22 限制的表大小为4GB.由于在MySQL 3.23 中使用了MyISAM 存储引擎,最大表尺寸增加到了65536TB(2

MySQL实战 | 01-当执行一条 select 语句时,MySQL 到底做了啥?

原文链接:当执行一条 select 语句时,MySQL 到底做了啥? 也许,你也跟我一样,在遇到数据库问题时,总时茫然失措,想重启解决问题,又怕导致数据丢失,更怕重启失败,影响业务. 就算重启成功了,对于问题的原因仍不知所以. 本文开始,记录学习<MySQL实战45讲>专栏的过程. 也许有人会问,你记录有什么意义?直接看专栏不就行了吗?你这不是啃别人的剩骨头吗? 是的,这个系列,我只是基于专栏学习,但是我会尽量从我的角度搞懂每一个知识点,遇到不懂得也会将知识点进行拆分. 我知道关注公众号的小伙

【mysql】关于innodb中MVCC的一些理解

一.MVCC简介 MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能 读锁:也叫共享锁.S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁.这保证了其他事务可以读A,但在T释放

关于mysql行级锁中MVCC的一些理解

一.MVCC简介 MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能 读锁:也叫共享锁.S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁.这保证了其他事务可以读A,但在T释放

浅析Mysql的隔离级别及MVCC

一.Mysql的四个隔离级别 预备工作: 先创建一个test数据库及account表, create database test;use test; create table account( id int not null, balance float not null, PRIMARY KEY ( id) ) 向account中插入两条测试数据 INSERT INTO table(id,balance) VALUES (1,1000); INSERT INTO table(id,balanc

MySQL到底能有多少个字段

今天技术讨论群里 “一切随遇而安”同学看书时出现一个疑问,一个MySQL的表中到底可以有多少个字段?带着这个疑问,我们展开了探讨,也接着讨论了一个单字段长度的问题. 1.  官方文档说明 官方文档的内容如下,主要意思是字段个数限制达不到理想的4096个,且和字段类型有关,innodb引擎的字段上限是1017,. 2.  测试表字段数限制 2.1  测试innodb引擎表 因官方文档介绍了innodb表字段限制是1017,因此可以写程序进行模拟.思路如下: a) 创建一张1个 char(1) 类型

MariaDB和mySQL到底区别在哪,实验说明问题!

先看图,插入数据和时间的对数图,实验条件一直且关闭了mysql默认事务保证不是单条事务而是批量事务 另外确保了mysql and mariaDB都是在支持事务存储引擎下测试的. MySQL之父Widenius先生离开了Sun之后,觉得依靠Sun/Oracle来发展MySQL,实在很不靠谱,于是决定从新开发代码全部开源免费关系型数据库,这就是MariaDB.在navicat中操作mariaDB的界面和提示符还是mysql端口也是3306 外人看起来除了数据库名字改了其他和mysql完全一致,但是看

MySQL多版本并发控制分析

读未提交时,读事务直接读取主记录,无论更新事务是否完成 READ_COMMITTED 读提交时,读事务每次检查主记录上有没有锁,如果没有锁就读取主记录:如果有锁,就读取undo log中最近的版本.这样每次读到的都是最新COMMITTED的数据.因此两次对同一字段的读可能读到不同的数据(幻读),但能保证每次都读到最新的数据. REPEATABLE_READ 第一次读的时候检查主记录上有没有锁,如果没有锁就读取主记录:如果有锁,就读取undo log中最近的版本.我猜测update的时候创建新的记

MySQL - 日志类型

七种日志文件 MySQL中有七种日志文件,分别是: 重做日志(redo log) 回滚日志(undo log) 二进制日志(binlog) 错误日志(errorlog) 慢查询日志(slow query log) 一般查询日志(general log) 中继日志(relay log) 其中重做日志和回滚日志与事务操作息息相关,二进制日志也与事务操作有一定的关系,这三种日志,对理解 MySQL 中的事务操作有着重要的意义.这里简单总结一下这三个具有一定相关性的日志. 重做日志(redo log)