MySQL事务一致性机制和MVCC多版本并发

本文为转载,并微调了图和个别文字说明。

我们都知道关系型数据库事务能保证数据一致性,那数据库到底是怎么设计事务这一特性的呢?

一、MySQL事务模型ACID

MySQL是一个多引擎数据库,其中InnoDB支持数据库事务,也是最常用的引擎。下边就介绍InnoDB的事务模型

MySQL官方文档对事务是这么描述的“事务是可以提交或回滚的原子工作单元。当事务对数据库进行多个更改时,要么提交事务时所有更改都成功,要么回滚事务时撤消所有更改。”

“ACID模型是一组数据库设计原则,强调业务数据和关键应用程序的可靠性很重要。MySQL包含与ACID模型紧密结合的innodb存储引擎组件,确保数据不会被破坏,结果不会被软件崩溃和硬件故障等异常情况所篡改。当您依赖ACID的特性,就不再需要重新发明一致性检查和崩溃恢复机制。”

ACID模型按照字母拆解分为四大特性

A : atomicity 原子性。原子性是我们对事务最直观的理解:事务就是一系列的操作,要么全部都执行,要么全部都不执行。

C : consistency 一致性。数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。例如对银行转帐事务,不管事务成功还是失败,应该保证事务结束后ACCOUNTS表中Tom和Jack的存款和不变。

I : isolation 隔离性。在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。

D : durability 持久性。只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

二、InnoDB存储引擎架构

下边这张图是InnoDB的架构,包括两大部分,内存结构(In-Memory Structures)和磁盘上的结构(On-Disk Structures)。

在这张图中,尤其要关注“Redo Log”和“Undo Tablespaces”这两个区域,它们跟事务息息相关。 InnoDB architecture diagram showing in-memory and on-disk structures.

 注:上来源于官方文档,MySQL5.7版本。在MySQL 8.0中 undo已经单独出来,有单独的undo文件,文件名为undo_001、undo_002。

内存结构(In-Memory Structures)更多的目的是在提高性能,因此本文不会过多关注。如果感兴趣,可以访问MySQL的官方网站www.mysql.com

“Undo Tablespaces”包含Undo Log(撤消日志),Undo Log是撤消日志记录的集合,其中包含如何撤消事务对聚集索引记录的最新更改的信息。Undo Log存在于撤消日志段中,这些日志段包含在回滚段中。

MySQL事务的四个特性中ACD三个特性是通过Redo Log(重做日志)和Undo Log 实现的;而 I(隔离性)是通过Lock(锁)来实现。

三、MVCC多版本并发

MVCC,Multi-Version Concurrency Control,多版本并发控制。这项技术使得InnoDB的事务隔离级别下执行一致性读操作有了保证,换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值。这是一个可以用来增强并发性的强大技术,查询不用等待另一个事务释放锁。这项技术广泛应用于数据库,例如Oracle,PostgreSQL。当然也有一些数据库产品以及mysql的其它存储引擎不支持它。

看一看MVCC机制的示意图,图下边会给出文字解释 

图中底部横轴是时间,纵向的箭头用来标记增、删、改、查发生的时刻。尤其注意时间轴上方两条色块,代表数据的两个版本V1、V2。为了醒目,我把V1、V2用红色方框圈了起来(多版本的体现)。从左向右解读这张图

1、T1事务插入数据a=3,然后提交,生成了数据对应的V1版本

2、T2事务开始读取a数据,读取会持续一段时间,由于开始读取的时刻,只有V1版本,所以最终T2读到a=3

3、T2读取过程中,T3对数据a进行修改,a=4,生成a数据的V2版本,但此时并未提交,因此生效的是V1版本数据。

4、T3修改提交之前,T4读取a数据,由于此时V1版本数据生效,因此,T4读到a=3

5、T3提交a=4的修改,V1版本数据失效,V2生效。a的值变为4

6、T5读取a的值,读到V2版本,a=4

注意:T2、T4的事务结束之前,读到的都是a=3(即使T3已经提交了,select数据也不会变化)。这就是MySQL默认的事物隔离级别“REPEATABLE READ可重复读” 的实现原理。 与OracleDB数据库的“READ COMMITTED读已提交” 的隔离级别 是不同的。

至此,MVCC的概念就搞明白了,那么怎么实现的呢?

实现MVCC有两种思路:

  • 第一种:写新数据时,把老数据拷贝复制到一个单独的位置(Undo空间),并对原位置的数据更新为新数据;在未commit提交时,其他用户select查询时从Undo空间中把老数据读出来。
  • 第二种:写新数据时,老数据不更新,而是把新数据插入进来;在未commit提交时,其他用户select查询时从原位置把老数据读出来。这种思路无需Undo空间,有点类似Java中GC垃圾回收的思想。

MySQL、Oracle中采用第一种思路;PostgreSQL中采用第二种思路。

与第一种思路相比,第二种思路的优势是:无论事务有多少操作,事务回顾可以立即完成;可以进行很多数据更新,无需考虑Undo空间用光的情况。 但第二种思路的劣势为:旧数据需要清理(在PostgreSQL称为VACUUM);获取旧版本的数据略慢些。

具体MySQL实现方式如下:

四、InnoDB多版本的实现

1、三个隐藏字段

在内部,InnoDB向数据库中存储的每一行数据添加三个字段。

(1)DB_ROW_ID字段,6字节。包含一个随着新行插入而单调增加的行ID,如果innodb自动生成聚集索引,则该索引包含行ID值。否则,DB_ROW_ID列不会出现在任何索引中。

(2)DB_TRX_ID字段,6字节。表示插入或更新行的最后一个事务的事务标识符。此外,删除在内部被视为更新,其中行中的特殊位被设置为将其标记为已删除。

(3)DB_ROLL_PTR字段,7字节,叫做回滚指针(roll pointer)。回滚指针指向写入回滚段的撤消日志(Undo Log)。如果行已更新,则撤消日志包含重建更新前该行内容所需的信息。

2、多版本产生过程

以新增一条记录并对该记录进行2次修改来说明具体实现 

这条记录有3个隐含字段(前面已经介绍),分别应对行的ID、事务号和回滚指针。

当插入的是一条新数据时,记录上对应的回滚段指针为NULL 

这个过程做了以下几件事

  • 用排他锁锁定该行

  • 把该行修改前的值拷贝到Undo Log中

  • 修改当前行的值,填写事务编号,使回滚指针指向Undo Log中的修改前的行

  • 记录Redo Log,包括Undo Log中的变化 

  多次更新后,回滚指针会把不同版本的记录串在一起。在InnoDB中存在purge线程,它会查询那些比现在最老的活动事务还早的Undo Log,并删除它们,从而保证Undo Log文件不至于无限增长。

3、提交与回滚

当事务正常提交时,InnoDB只需要更改事务状态为commit即可,不需要做其他额外的工作

回滚(rollback)需要根据当前回滚指针从Undo Log中找出事务修改前的版本,并恢复。如果事务影响的行非常多,回滚则可能会很慢,根据经验值没提交的事务行数在1000~10000之间,InnoDB效率还是非常高的(唐成-数据库多版本实现内幕)。

commit效率高,rollback代价大

4、可见性

事务隔离是数据库处理的基础之一,隔离是缩写ACID中的I。隔离级别是当多个事务同时进行更改和执行查询时,微调性能、可靠性、一致性和结果再现性之间的平衡的设置。

InnoDB提供SQL1992标准定义的四个隔离级别,READ UNCOMMITTED(未提交读), READ COMMITTED(已提交读), REPEATABLE READ(可重复读), and SERIALIZABLE(可串行化)。默认的是REPEATABLE READ

每种隔离级别具体的意义可以百度查到,实现原理深入进去比较复杂。注意到每条数据隐藏的事务ID字段DB_TRX_ID有时序性,理论上可以根据一些策略,借助这个字段来实现与隔离级别相关的功能。事实上InnoDB也是这么做的。当然这个功能还涉及很多锁的问题,这里不再展开。

5、REPEATABLE READ实现

 利用MVCC机制,可以轻松实现重复读。

    1、开始一个事务,读取数据。会读取已经commited的数据,同时拿到数据行对应的DB_TRX_ID,

    2、当再次读取数据时,会将已拿到的DB_TRX_ID与表中数据行对比。与表中该行的DB_TRX_ID一致,则直接读取;

    3、若表中该行的DB_TRX_ID大于已经拿到DB_TRX_ID,则通过undo信息逐级查找,直到找到相等的DB_TRX_ID,然后从undo信息中读出对应的数据。

    4、这样就利用MVCC实现了REPEATABLE READ隔离级别。

MySQL官方文档在“锁和事务模型”这一章节开始就介绍了InnoDB的锁,MySQL :: MySQL 5.7 Reference Manual :: 14.7.1 InnoDB Locking,感兴趣可以去读一下。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值