MYSQL---MVCC

通过本文将弄明白以下几个问题。

一、思考

问题一:MVCC是什么?主要为了解决什么问题?

问题二:MVCC的实现原理是什么?

问题三:有了MVCC之后,开发者还需要做什么?

二、分析 

2.1概念

该定义由此博文总结:https://draveness.me/database-concurrency-control

MVCC,即多版本并发控制,在这个机制中,每一个写操作都会创建一个新版本的数据,每一个读操作将从有限多个版本的数据中选择一个最合适的返回,而管理和快速挑选数据的版本就是MVCC的工作。

其主要目的在于解决数据库的“并发读写”能力。

2.2举例

在博文【MYSQL---锁】中,我们提到在更新数据时,InnoDB引擎会默认为数据加X锁,此时其它事务不能对该数据行加X锁和S锁,但是能读取该数据,这便提高了数据库的“并发读写”能力,如下:

假设有表t,字段(id,name);事务A和B。初始数据(1,'a');事务隔离级别为RR。

事务A事务B

SET autocommit=0;

BEGIN;

SET autocommit=0;

BEGIN;

 

//步骤一(获取X锁)

UPDATE t SET name='b' WHERE id=1;  //name=b

//步骤二(依然可读,读写并发,MVCC控制)

SELECT name FROM t WHERE id=1; // name=1;

 

.....

COMMIT;

.....

COMMIT;

由上图可知,事务B虽然拥有了数据行的X锁,事务A虽然不能再加X锁和S锁,但是仍然可读数据(这是RR隔离级别的特性,所以说可以说MVCC可以控制采用RR或者RC,请看后文分析),而且读到的数据是事务开始时的数据,这便解决了“并发读写”问题,那MVCC到底是怎么做的呢,请看下文分析。

2.3MVCC实现原理

要解决上述所说的“并发读写问题”,那么事务A读到的数据必然和事务B修改的数据不是同一份数据,在MVCC中,可以称为不是同一版本的数据。那么就有两个问题需要我们思考:

1.如何构建多版本?

2.事务A如何知道选择哪个版本数据?

在看下文之前,您可以看一下这篇文章:https://juejin.im/post/5c68a4056fb9a049e063e0ab,其通俗易懂的解释了多版本并发控制(MVCC)中的增删查改操作。

InnoDB默认会给每行加三个字段

InndoDB在创建表时,会默认为表增加三个字段,其中有两个字段是与MVCC相关的:

DB_TRX_ID(下文中以tx_id表示):数据行的版本号,表示该条数据最后被修改时的事务id。

DB_ROLL_PT:删除版本号,表示该条数据被删除时的事务id。

快照

首先在事务系统中,会维护一个全局的活跃事务id(descriptors)。

当要创建一个快照(readview)会将上述的全局的活跃事务id拷贝一份到新建的快照(数据结构见下文)。当事务内根据条件查询某条数据时,可能会查询到数据的多个版本,这时对于查询出来的每一条数据,都会根据快照判断其是否满足可见性(可以被当前事务看见),如果可以则返回,否则就利用undolog来构建历史版本数据,直到构建到最老的版本或者可见性满足。

所以快照的主要作用就是“数据可见性判断”。

快照的数据结构(模拟):

readview{

int [] descriptors:数组,存储数据库中所有活跃事务(已经开始,但未提交;不包含只读事务)的事务id,id从小到大排序。

int up_limit_id:descriptors数组中最小的值。

int low_limit_id:创建快照时产生的最大事务id(max_trx_id),该值一定大于descriptors数组中的最大事务id。

}

数据可见性判断

有了前面介绍的两个知识点,我们现在来分析一下,MVCC是如何判断数据是否可见的。

其会用当前数据行的tx_id和快照中的up_limit_id和low_limit_id进行比较:

  1. 如果tx_id<up_limit_id:表示这条数据最后修改在快照被创建之前,因此可见。
  2. 如果tx_id>=low_limit_id,表示这条数据最后修改在快照被创建之后,因此不可见。
  3. 如果up_limit_id<=tx_id<low_limit_id,表示这条数据在快照创建之时,由其它活跃事务修改,因此不可见。
  4. 如果tx_id不在descriptors数组之中,经过前面的判断,这种情况可能存在于 descriptors数组中最大值<tx_id<low_limit_id,因为有了第三条,所以这里仍然表示这条数据最后修改在快照被创建之前,因此可见。或者descriptors数组为空,不能存在活跃性事务,那说明修改这条数据的事务已经全部提交,因此可见。

经过以上的判断,如果仍然未找到可见性数据,则通过undolog去构建老版本数据直到找到可以被看见的数据或者undolog被解析完毕。

可重复读、读已提交与MVCC关系

其实可重复读和读已提交的根本不同在于数据可见性,“可重复读”在事务提交之后,数据仍然不能被其他事务可见。而“读已提交”,在事务提交之后,数据就能被其他事务看见。这里根本原因是因为MVCC实现中,创建快照的时机不同:

可重复读(RR):快照在第一次查询的时候创建,这个快照会一直持续到事务结束,期间数据可见性不会改变,所以在当前事务内不会产生数据不一致情况。

读已提交(RC):事务中的每个查询都会创建一个快照,这样如果两个查询之间,有其他事务修改了数据,就导致数据可见性改变,就会产生数据不一致情况,所以不可重复读。

通过MVCC再分析前面案例

上文案例如下:

背景:

假设有表t,字段(id,name);事务A和B。初始数据(1,'a');事务隔离级别为RR。全局活跃事务id(descriptos)中为空,此时该行数据情况如下:

 

idnametx_idDB_ROLL_PT
1a1(前面已经有事务id为1的事务修改了该数据,但是已经提交)NULL

过程:

事务A和事务B执行步骤如下,假设事务A的id为3,事务B的id为2,即事务B先与事务A执行:
事务A事务B

SET autocommit=0;

BEGIN;

SET autocommit=0;

BEGIN;

 

//步骤一(获取X锁)

UPDATE t SET name='b' WHERE id=1;  //name=b

//步骤二(依然可读,读写并发,MVCC控制)

SELECT name FROM t WHERE id=1; // name=1;

 

.....

COMMIT;

.....

COMMIT;

分析:

1.当事务B执行步骤一之后:

该数据行情况:

idnametx_idDB_ROLL_PT
1a12(代表事务B删除了数据)
1b2(事务B修改了数据)NULL
全局活跃事务id(descripotors数组)=[2],因为事务B还未提交。

2.事务A执行步骤二:

事务A根据id=1查询这条数据,先创建快照如下:

readview{

descripotors:[2,3],即事务A和事务B。

up_limit_id:2,descripotors数组中最小值。

low_limit_id:3,当前事务A的事务id。

}

根据id=1查询到上述两个版本数据,遍历这两条数据,按照上面的“可见性分析”规则,结果为:

第一条数据满足第一条:tx_id<up_limit_id,所以这条数据满足可见性,返回。

通过上面的分析相信你已经可以解答开篇的几个问题,其实弄懂原理之后,并不难。MVCC分析至此结束,如有不对之处,请指正。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

参考文章:

  1. 五分钟搞懂MVCC机制:https://juejin.im/post/5c68a4056fb9a049e063e0ab
  2. MVCC多版本并发控制:https://segmentfault.com/a/1190000012650596
  3. 淘宝数据库内核月报-InnoDB事务系统-MVCC:http://mysql.taobao.org/monthly/2017/12/01/
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值