MVCC实现原理

53 篇文章 2 订阅
39 篇文章 1 订阅

现在,主流关系型数据库产品基本都实现了MVCC的特性,快照在MVCC中起着重要的作用,代表某一时刻数据的版本,它是实现一致性读的基础。在更新操作没提交前,数据的前镜像文件存储在Undo日志中,利用Undo日志可以实现一致性读,事务回滚以及异常恢复等操作,下面就聊聊MySQL事务,MVCC,快照及一致性读的原理与实现。


MySQL中的事务

事务在RDBMS系统中概念基本都是一样的,是由一组DML语句构成的基本工作单元,这组语句要么全部成功,要么全部失败。
在这里插入图片描述开发过程中,比较关心长事务,即包含DML语句多的工作单元,事务太长会导致一些错误,例如可能由于事务数据包大小超过参数max_allowed_packet设置会导致程序报错,也可能有事务中某个SQL对应接口报错,导致整个服务调用失败,在程序设计时,应该避免长事务带来的业务影响。


事务的ACID

在这里插入图片描述
原子性是事务隔离的基础,隔离性和持久性是手段,最终目的是为了保持数据的一致性。

数据库的一致性:是指数据库从一个一致性状态变到另一个一致性状态。


事务的并发问题

  • 脏读:事务A读取了事务B未提交的数据。
  • 不可重复读:事务A多次读取同一份数据,事务B在此过程中对数据修改并提交,导致事务A多次读取同一份数据的结果不一致。
  • 幻读:事务A读取数据的同时,事务B插入了一条或者多条数据,当事务A提交后发现有新数据被提交,产生了幻觉。

不可重复读侧重于update或者delete操作,幻读侧重于insert。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。可以用行锁解决不可重复读,用间隙锁解决幻读,这里的幻读指的是当前读。


事务隔离级别

事务隔离是数据库处理的基础之一,隔离级别在多个事务同时进行更改和执行查询时,对性能与结果的可靠性、一致性和可再现性之间的平衡进行调整,InnoDB利用不同的锁策略支持不同隔离级别。MySQL中有四种隔离级别,分别是读未提交(READ UNCOMMITTED),读已提交(READ COMMITTED),可重复读(REPEATABLE READ)以及串行化(SERIALIZABLE)。mysql的默认隔离级别是可重复读

在这里插入图片描述


InnoDB并发控制


MVCC特性

InnoDB是一个支持行锁的存储引擎,为了更好的支持并发,使用了非锁定读的方式,不需要等待访问数据上的锁释放,而是读取行的一个快照,该方法是通过InnoDB MVCC特性实现的。

MVCC是Multi-Version Concurrency Control的简称,即多版本并发控制,作用是让事务在并行发生时,在一定隔离级别前提下,可以保证在某个事务中能实现一致性读,也就是该事务启动时根据某个条件读取到的数据,直到事务结束时,再次执行相同条件,还是读到同一份数据,不会发生变化。


MVCC的优点

读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,可以增加系统的并发性能。


快照读和当前读

在MVCC中,有两种读操作:快照读和当前读。如何区分快照读和当前读呢? 可以简单的理解为:

  • 快照读:简单的 select 操作,属于快照读,不需要加锁。
  • 当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。

在这里插入图片描述


MVCC算法思想

MVCC使用时间戳 (TS), 或“自动增量的事务ID”实现“事务一致性”。MVCC可以确保每个事务(T)通常不必“读等待”数据库对象§。这通过对象有多个版本,每个版本有创建时间戳 与废止时间戳 (WTS)做到的。

事务Ti读取对象§时,只有比事务Ti的时间戳早,但是时间上最接近事务Ti的对象版本可见,且该版本应该没有被废止。

事务Ti写入对象P时,如果还有事务Tk要写入同一对象,则(Ti)必须早于(Tk),即 (Ti) < (Tk),才能成功。

MVCC可以无锁实现。


MVCC快照(Read View)

MVCC内部使用的一致性读快照称为Read View,在不同的隔离级别下,事务启动时或者SQL语句开始时,看到的数据快照版本可能也不同,在RR、RC隔离级别下会用到 Read view

InnoDB 里面每个事务有一个唯一的事务ID,称为Transaction ID,它是在事务开始的时候向InnoDB的事务系统申请的,是按申请顺序严格递增的。而每行数据都有多个版本。每次事务更新数据的时候,都会生成一个新的数据版本Read View,并且把Transaction ID赋值给这个数据版本的事务 ID,标记为 db_trx_id。同时旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它,数据表中的一行记录,其实可能有多个数据版本 ,每个版本有自己的 db_trx_id。

ReadView的本质就是一个数据结构,在事务开始的时候会根据事务链表构造一个ReadView。


InnoDB行格式

InnoDB表数据的组织方式为主键聚簇索引,二级索引中采用的是(索引键值, 主键键值)的组合来唯一确定一条记录。

InnoDB表数据为主键聚簇索引,mysql默认为每个索引行添加了4个隐藏的字段,分别是:

隐含 ID(DB_ROW_ID):InnoDB引擎中一个表只能有一个主键,用于聚簇索引,如果表没有定义主键会选择第一个非Null的唯一索引作为主键,如果还没有,生成一个隐藏的DB_ROW_ID作为主键构造聚簇索引。

事务ID(DB_TRX_ID):最近更改该行数据的事务ID。

回滚指针(DB_ROLL_PTR):undo log的指针,用于记录之前历史数据在undo log中的位置。

删除标志(DELETE BIT):索引删除标志,如果DB删除了一条数据,是优先通知索引将该标志位设置为1,然后通过(purge)清除线程去异步删除真实的数据。

在这里插入图片描述

整个MVCC的机制都是通过DB_TRX_ID和DB_ROLL_PTR这2个隐藏字段来实现的。


事务链表

当一个事务开始的时候,会将当前数据库中正在活跃的所有事务(执行begin,但是还没有commit的事务)保存到一个叫trx_sys的事务链表中,事务链表中保存的都是未提交的事务,当事务提交之后会从其中删除。

在这里插入图片描述

RC和RR隔离级别ReadView的实现方式的区别

我们知道,RC隔离级别是能看到其他事务提交后的修改记录的,也就是不可重复读,但是RR隔离级别完美的避免了,但是它们都是使用的MVCC机制,那又为何有两种截然不同的结果呢?其实我们看一下他们创建ReadView的区别就知道了。

在RC事务隔离级别下,每次语句执行都关闭ReadView,然后重新创建一份ReadView。
在RR下,事务开始后第一个读操作创建ReadView,一直到事务结束关闭。

上面的总结英文版为:With REPEATABLE READ isolation level, the snapshot is based on the time when the first read operation is performed. With READ COMMITTEDisolation level, the snapshot is reset to the time of each consistent read operation.
来源自MySQL官网:MySQL Glossary-glos_consistent_read

因为RC每次查询语句都创建一个新的ReadView,所以活跃的事务列表一直在变,也就导致如果事务B update提交了后事务A才进行查询,查询的结果就是最新的行,也就是不可重复读咯。而RR则一直用的事务开始时创建的ReadView。


MVCC多版本实现

为了更直观地理解 MVCC 的实现原理,这里举一个“事务对某行记录更新的过程”的案例来讲解 MVCC 中多版本的实现。

假设 F1~F6 是表中字段的名字,1~6 是其对应的数据。后面三个隐含字段分别对应该行的隐含ID、事务号和回滚指针,如下图所示

在这里插入图片描述

【更新过程】

首先,假如这条数据是刚 INSERT 的,可以认为 ID 为 1,其他两个字段为空。

然后,当事务 1 更改该行的数据值时,会进行如下操作,如下图所示

在这里插入图片描述

  • 用排他锁锁定该行;记录 Redo log;
  • 把该行修改前的值复制到 Undo log,即图中下面的行;
  • 修改当前行的值,填写事务编号,使回滚指针指向 Undo log 中修改前的行

接下来,与事务 1 相同,此时 Undo log 中有两行记录,并且通过回滚指针连在一起。因此,如果 Undo log 一直不删除,则会通过当前记录的回滚指针回溯到该行创建时的初始内容,所幸的是在 InnoDB 中存在 purge 线程,它会查询那些比现在最老的活动事务还早的 Undo log,并删除它们,从而保证 Undo log 文件不会无限增长,如下图所示

在这里插入图片描述


事务回滚

insert:反向操作是delete,undo里记录的是delete相关信息,存储主键id即可。

udpate:反向操作是update,undo里记录的是update前的相关数据。

delete:反向操作是insert,undo里记录的是insert values(……)相关的记录。

从这里可以知道,更新操作占用Undo空间的大小排序如下:

delete > update > insert

所以不建议物理delete删除数据,会产生大量的Undo Log,Undo快被写满就会发生切换,在次期间会有大量的IO操作,导致业务的DML都会变得很慢。


总结

介绍了MySQL事务,快照,MVCC以及Undo,虽然这些东西比较抽象,但是搞清楚这些东西是一件很有意义的事,能够帮助我们更好的理解和使用MySQL,也可以把这种设计思想用在自己业务系统中。其中Undo在MySQL中的作用很重要,它是MVCC能够快速创建快照基础,支撑系统的高并发。MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值