基于MVCC,我用C++自己手撸了个MySQL!
2022-04-22 15:19:24来源:冰河技术
没错,真如标题所示,我基于MVCC算法(这里我姑且叫它算法吧,毕竟在实际写代码时,确实是利用算法实现的),使用C++写了个简易版的MySQL,实现了简易版的CRUD操作。
其实,今天我并不打算先向小伙伴们演示我写的简易版MySQL,这个项目待我再优化下,会开源出来的,到时大家可以一起学习,一起进步,一起来维护它。
今天,我想跟大家重点聊聊MVCC,网上关于MVCC的文章很多,大部分都是基于版本链进行介绍的,其实对于初学者来说,使用版本链介绍MVCC其实还是挺难理解的。今天,我就来跟大家聊聊我是如何理解MVCC的,MVCC其实很简单,不用版本链你也可以彻底理解透彻。
MVCC技术MVCC是一种通过记录数据的历史版本来提升事务并发处理能力的一项技术,它能够极大的提升在并发事务下数据的处理性能,目前,大部分关系型数据库都实现了MVCC机制。
MVCC主要解决多事务并发控制问题,也就是保证事务的隔离性。
MVCC的存储方式MVCC大体上可以分为三种存储方式,分别为Append-Only方式、Delta方式和Time-Travle方式,如下所示。
(1)Append-Only方式:将数据的历史版本直接存储在数据表中,代表数据库为PostgreSQL。
(2)Delta方式:将数据的增量历史版本存储在独立的表空间,代表数据库为MySQL和Oracle。
(3)Time-Travle方式:将数据的每个版本都全量存储下来,代表数据库为HANA。
MVCC的工作原理MVCC主要用来保证事务的隔离性,这里,我们就分别以读已提交和可重复读两种隔离级别为例,来聊聊MVCC是如何工作的。
读已提交MVCC的工作原理在读已提交隔离级别下,当前事务只能看到两类数据,如下所示。
(1)当前事务自身产生的数据。
(2)当前事务开启之前,其他已经提交的事务所产生的数据。
为了便于小伙伴们理解,这里我画了一张简易的事务执行图,如下所示。
事务A到事务E是在数据库中执行的五个事务,它们按照先后顺序执行,分别操作的是数据表中data1~data5的五条记录。在t1时刻,启动事务E,事务E要读取事务A到事务D的这四条记录,在t1时刻,事务E启动时,会向系统申请一个活动事务列表,所谓的活动事务,就是已经启动但是并未提交或者回滚的事务。
所以,在申请的活动事务列表中会看到事务D,当事务E查询到data4这条数据记录时,其对应的事务D正好在活动事务列表中,事务E就会读取data4的上一个版本。
而事务A、事务B和事务C在事务E启动时已经提交,并且最新版本的事务id小于活动事务D对应的事务id,所以事务E能够看到事务A、事务B和事务C对应的data1、data2和data3记录的最新版本。
可重复读MVCC的工作原理在重复读隔离级别下,MVCC又是如何工作的呢?先来看张图。
如果在读已提交隔离级别下,则在t1时刻,事务E启动时,事务A、事务B和事务C已经提交,所以,事务E能够读取到事务A、事务B和事务C对应的data1、data2和data3记录的最新版本。而事务D属于活动事务,所以,事务E能够读取到data4的上一个版本。
事务E执行到t2时刻时,事务D也已经提交,按照之前的分析可知,在t2时刻,事务E能够读取到事务A、事务B、事务C和事务D对应的数据data1、data2、data3和data4的最新版本。
在可重复读隔离级别下,这显然是不符合要求的。
在可重复读隔离级别下,MVCC机制是如何解决这个问题的呢?
其实解决的办法很简单,就是在系统中记录下t1时刻启动事务E时的活动事务列表,在事务E执行的过程中,一直使用在t1时刻记录的活动事务列表即可,这个一直使用的活动事务列表被称为“快照”。
很显然,在t2时刻使用在t1时刻保存的活动事务列表,则事务E在t1时刻和t2时刻读取到的数据是一致性。
读已提交与可重复读MVCC的区别读已提交隔离级别下每个SQL语句都会有一个自己的快照,它们看到的数据库中的数据是不同的。而在可重复读隔离级别下,所有的SQL语句使用同一个快照,能够看到数据库中同样的数据。
快照优化在实现MVCC时,并只是简单的存储事务id列表,而是会统计最小活动事务id和最大已提交事务id,这样做的好处是:大部分事务id通过比较这些边界值就能够迅速判别是读取最新版本还是上一个版本,如果事务id正好落在这些边界值的范围之内,则只需要进一步查找当前事务id是否与活动事务的id相匹配即可。如果相匹配,则说明当前事务是活动事务,可以看到当前数据。
好了,关于MVCC,小伙伴们,你们理解了吗?理解透彻后,再学习下MySQL的底层原理,有条件的话,阅读下MySQL的源码,然后跟冰河一起手写MySQL。