mysql详情之MVCC由浅入深

概念

MVCC(Multiversion Concurrency Control):多版本并发控制。是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。

MVCC的实现思路

  1. 修改数据时,都对应一个修改者所属的事务【前提条件】

  2. 每个事务都有自己的事务编号【前提条件】

  3. 每条数据里都有个隐藏字段,可以用来存修改者事务编号【前提条件】

  4. 每次修改时,保留原数据,产生一条新版本数据。并且在新数据里的隐藏字段存上修改者事务编号A【前提条件】

  5. 当另一个事务B要去访问那个数据时,就可以根据访问者的事务编号B去判断并选取哪个版本的数据【实现MVCC】

  6. 至于怎么选取,还有一个“场外配置项”来控制:隔离级别【配置】

至此我们就知道了MVCC基本原理。

mysql实现的MVCC逻辑

见文章Mysql的MVCC的理解(这篇文章简洁而逻辑清晰,就不重复了)

这里就着重讲解以下几点

四种隔离级别

  • RU(READ UNCOMMITTED)读未提交
  • RC(READ COMMITTED)读已提交
  • RR(REPEATABLE READ)可重复读
  • SERIALIZABLE 串行化

版本链:就是用来把各个版本的数据连起来,保存起来。供访问者从新到旧的顺序筛选,直到找到自己能看的数据

ReadView:一个非常重要且比较难理解的概念。表示当前活跃的事务id列表(已开始,还未提交),这是个静态数据,生成了就不再变。

根据我们前面说的mvcc基本原理。大致的朴素设想:隔离级别应该是用来控制算法走向的。

但mysql的实现更加巧妙。算法中用到了ReadView这个数据,隔离级别是用来控制ReadView的。实现了算法不变,算法用到的数据根据配置改变(优美的代码结构)

逻辑场景:ReadView是访问者产生的。每次访问数据,访问者自己就生成一个ReadView,拿在手里。然后去版本链里筛选数据。

隔离级别的控制方式:RC的时候,是整个事务里,每次select都会重新产生一个ReadView。RR的时候,是访问者第一次select时生成了ReadView,整个事务里一直用它(虽然RR的隔离级别更高,但相对来说实现起来反而更简单,更快。默认用这个级别大概也有层意思)。

注意:上面的“select”指的是“select 同一张表”(RC时,即便两次单独查不同行,也会使用同一个ReadView。但查不同表就一定是两个ReadView)。

实验

这是一个很经典的观察ReadView生成逻辑的简单实验。

实验材料

  1. MySql5.7

  2. 一个表,名叫staff。字段:id | name | age | pos | salary

操作:开两个命令行窗口,都打开msql命令行窗口,分别开两个事务A,B;A读 B改数据

目标:观察一个可能会被误解的现象(如果懂了上面的原理,就很容易理解了)

前提

  1. 关掉事务自动提交 set global autocommit=0;

  2. 保持mysql的默认隔离级别RR不要动

实验1(命令前的数字代表操作顺序号)

事务A事务B
1. begin; 【开始事务A】2. begin;【开始事务B】
3. select * from staff;【观察到:id为3的age=20】
4. update staff set age=30 where id=3;
5. commit;【结束事务B】
6. select * from staff;【观察到:id为3的age=20】

这个现象很好理解,符合我们心中RR的印象:一个事务里,查询到的数据不变,所以事务A感知不到B对数据的修改。

实验2(对照组)

事务A事务B
1. begin; 【开始事务A】2. begin;【开始事务B】
3. update staff set age=40 where id=3;
4. commit;【结束事务B】
5. select * from staff;【观察到:id为3的age=40】
6. commit;【结束事务A】

这个现象就可能不太符合我们对RR的理解了。事务A居然感知到B对数据的变化了。

实际上这是我们对RR的误解,RR的本意是:可重复读。即在一个事务里,无论读多少次都一样。并没有规定读到的是什么数据。

如果不了解原理,可能会觉得很神奇,甚至有点“量子力学”到感觉:是否观察居然影响到了结果。

实验2中之所以能读到事务B修改的数据,那是因为事务A在步骤5时才第一次生生成ReadView。

如果要验证RC的情况,就先改一下配置隔离级别:
//设置read uncommitted级别:
set session transaction isolation level read uncommitted;

//设置read committed级别:
set session transaction isolation level read committed;

//设置repeatable read级别:
set session transaction isolation level repeatable read;

//设置serializable级别:
set session transaction isolation level serializable;

如果要验证:“select”指的是“select 同一张表”。可以把两个select * from staff;换成不同的表,换成单独查两行,都是可以验证的。

至此,你已经理解mysql实现mvcc的关键原理。

深入细节

有了前面的铺垫,可以再看这篇文章(MySQL中MVCC的正确打开方式(源码佐证)),就不会有太大难度了。没啥可强调的,其实就是上面关键原理的具体的实现代码层面,挺全的,有兴趣的可以看看。

MVCC和二级索引

上面我们讲的数据版本链,都是数据行上的操作。那如果远离数据行的二级索引被改了,肯定也要读写并行。那么是怎么实现的?

其实上二级索引应对多版本问题,只简单实现了一小部分。当自己无法判断处理时,就先取出来,最后还是交给数据行的mvcc机制决定这条数据是否符合。具体实现方式:

  • 给每个索引都加一个隐藏字段(最后修改的事务ID)。再加一个索引链,用来挂改之前的老数据。
  • 修改索引字段时,不是直接改,而是新增一个索引数据,然后把老数据挂在索引链上。
  • 查询的时候,如果原来是判断等于xx = 数据,那么此时的判断逻辑就相当于变成了 xx in(新数据, 老数据1,老数据2)。如果查出来符合,但此时的数据还不能直接返回给用户,还需要根据主键找到具体数据行,最后根据上面mvcc的原理决定是否返回给用户。

具体看博客(mysql二级索引没有mvcc_MySQL InnoDB MVCC深度分析),或者直接看官网(Clustered and Secondary Indexes)。
这里你可能会产生的疑惑:二级索引的判断如何跟聚簇索引保持一致的?(二级索引可以不完全判断一个数据的可见性,但不能跟聚簇索引判断出来的结果矛盾。就像基层员工由于没经验,可以不完全知道怎么处理一件事,但他不能告诉客户一个跟领导完全相悖的答案)
他们能保证不出现自相矛盾的方法,靠的就是数据访问者手里的那个ReadView,这是二级索引和聚簇索引共同使用的判断数据版本的依据。

这里我发现一个很有意思的事情。以前我总觉得看不懂官网是自己英文太差。但实际上,可以看看这篇文章(原封不动翻译的官网文章)。如果不是看了前面那篇博客,这一坨中文同样让人看不懂。

其他

undo log到底是什么

前面我们说版本链的时候,说版本链是由各个版本的数据串起来的链。其实这个说法不准确。这串起来的其实是undo log。并不是数据行。

在学习redo log,undo log的时候,就会强调:redo log是物理日志,undo log是逻辑日志。undo log可以简单理解为sql语句。
通过这样的sql语句 和 新版数据 就可以得到上一个版本的老数据(减少空间浪费)。

快照读,当前读

在学mvcc时,还会“莫名其妙”的提到快照读,当前读这两个词。之所以说莫名其妙,因为粗看这两个词的功能,似乎和mvcc并没有很强的功能上的关联。你可能还会听到一致性非锁定读这个词,更觉得有点晕。

其实快照读就是非锁定读(Nonlocking Reads),当前读就是锁定读(Locking Reads)。我觉得还是直译为锁定读/非锁定读更直观好理解。因为这两个词直接区分的就是是否用锁。

无论是否用锁,我们都希望读到的数据不要受外界(其他写操作)干扰,变来变去。所以就加了个一致性
怎么实现一致性呢? 加锁的(当前读/锁定读)很好理解,隔离级别为串行化时,就是通过加锁,读写完全串行化,但性能很差。
那不加锁(快照读/非锁定读)呢?就是通过这里的主角MVCC机制实现。

由于锁定读肯定是一致的,所以官网的目录:一个叫“Consistent Nonlocking Reads”,一个叫“Locking Reads”(把一致性直接省略了)。这也是为什么你听过“一致性非锁定读”这个词,但没见过“一致性锁定读”这种说法。
事实上:mysql官网里,一致读(Consistent Read)就特指MVCC的在RC/RR两种隔离级别的情况。

乐观锁?

有人说MVCC是一种乐观锁的实现。这种说法是不对的
数据库中的乐观锁指的是两个写操作之间的实现方式。
改的时候

  1. 先查版本号(也可根据自己的业务情况,设计其他类型的唯一字段)。
  2. 修改,根据版本号是否变化,判断是否要保存(CAS机制),如果已经有其他写操作已经把版本号改了,那就保存不了。
select version from table;--得到oldV
update table set col=newData, vesion=(oldV + 1) where version=oldV and 其他条件;

乐观锁是用户自己根据业务场景,自己实现的CAS机制(比如实现在多线程或集群下对资源的抢占。一种简易的分布式锁)。mysql并没有内置这种乐观锁机制。

普通select

很多文章包括官网里,在说普通select的时候,其实指的都是包含在事务里的普通select语句
直接操作数据库,查select这种情况,相关文章里一般都不提。不加事务的select,查询只能读到提交后的数据,和mvcc机制没有关系。
看下面这个图(把上面涉及的概念整理在一起,梳理一下关系)

在这里插入图片描述

一致性

这个词很容易让人误解。我觉得根源在于中文里没有一个独立且合适的简单词汇来代表“一贯不变”。一致一致性 是两个看似一样,但意思却很不同的词,前者单纯的表示“一样”,后者是英文Consistent的翻译(一贯不变)。

在计算机行业里,即便是同样都用一致性,意思也会有细微差别。
数据库里的一致性一致性hash(还有强/弱/最终一致性) 也不太一样。前者指的是一个事物在经过一些列操作之后依然得到正确的结果。后者强调的是多个个体之间(横向),在一段时间里(纵向)表现都一致。
这种细微的含义差别,英文里也没有区分,都用的是Consistent

之所以说这个。因为有一次当我看到“一致性hash”的时候,第一反应是:用hash算法判断两个文件是否一致(类似于md5值)。
一查,发现自己理解错了。这其实是一种分布式集群中,让数据分发和路由保持均衡,并且易于节点伸缩的算法
我这种错误的理解的根源,就是没意识到“一致”和“一致性”是两个意思不同的词。导致之前虽然学过,时间一长,就很容易遗忘。
然后到数据库这里,也出现一致性(ACID的C,还有一致性非锁定读)。
强/弱/最终一致性是分布式协调服务中间件zookeeper那里会提到的。
他们意思都有细微差别,尤其是“一致性hash”这个名字最容易让人误解。

总结

  • MVCC是为实现隔离性的一种无锁方案。
  • 相比有锁方案,实现了读写并行处理,提高了系统性能。
  • 实现机制是多版本控制

参考

面试官:谈谈你对Mysql的MVCC的理解?

MySQL中MVCC的正确打开方式(源码佐证)

MySQL事务日志(redo log和undo log)的详细分析

mysql二级索引没有mvcc_MySQL InnoDB MVCC深度分析

Clustered and Secondary Indexes

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值