社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

MySQL 多版本并发控制机制(MVCC)源码浅析

数据分析与开发 • 6 年前 • 685 次点击  

(点击上方蓝字,快速关注我们)


来源:无毁的湖光-Al

my.oschina.net/alchemystar/blog/1927425?tdsourcetag=s_pcqq_aiomsg


前言


作为一个数据库爱好者,自己动手写过简单的SQL解析器以及存储引擎,但感觉还是不够过瘾。<>诚然讲的非常透彻,但只能提纲挈领,不能让你玩转某个真正的数据库。感谢cmake,能够让我在mac上用xcode去debug MySQL,从而能去领略它的各种实现细节。


笔者一直对数据库的隔离性很好奇,此篇博客就是我debug MySQL过程中的偶有所得。


(注:本文的MySQL采用的是MySQL-5.6.35版本)


MVCC(多版本并发控制机制)


隔离性也可以被称作并发控制、可串行化等。谈到并发控制首先想到的就是锁,MySQL通过使用两阶段锁的方式实现了更新的可串行化,同时为了加速查询性能,采用了MVCC(Multi Version Concurrency Control)的机制,使得不用锁也可以获取一致性的版本。


Repeatable Read


MySQL的通过MVCC以及(Next-Key Lock)实现了可重复读(Repeatable Read),其思想(MVCC)就是记录数据的版本变迁,通过精巧的选择不同数据的版本从而能够对用户呈现一致的结果。如下图所示:



上图中,(A=50|B=50)的初始版本为1。


1.事务t1在select A时候看到的版本为1,即A=50


2.事务t2对A和B的修改将版本升级为2,即A=0,B=100


3.事务t1再此select B的时候看到的版本还是1, 即B=50

这样就隔离了版本的影响,A+B始终为100。


Read Commit


而如果不通过版本控制机制,而是读到最近提交的结果的话,则隔离级别是read commit,如下图所示:



在这种情况下,就需要使用锁机制(例如select for update)将此A,B记录锁住,从而获得正确的一致结果,如下图所示:



MVCC的优势


当我们要对一些数据做一些只读操作来检查一致性,例如检查账务是否对齐的操作时候,并不希望加上对性能损耗很大的锁。这时候MVCC的一致性版本就有很大的优势了。


MVCC(实现机制)


本节就开始谈谈MVCC的实现机制,注意MVCC仅仅在纯select时有效(不包括select for update,lock in share mode等加锁操作,以及updateinsert等)。


select运行栈


首先我们追踪一下一条普通的查询sql在mysql源码中的运行过程,sql为(select * from test);



其运行栈为:



由于mysql默认隔离级别是repeatable_read(RR),所以read_record重载为 rr_sequential(当前我们并不关心select通过index扫描出row之后再通过condition过滤的过程)。继续追踪:



让我们看下该函数内部:


bool lock_clust_rec_cons_read_sees(const rec_t* rec /*由innodb扫描出来的一行*/,....){

...

// 从当前扫描的行中获取其最后修改的版本trx_id(事务id)

trx_id = row_get_rec_trx_id(rec, index, offsets);

// 通过参数(一致性快照视图和事务id)决定看到的行快照

return(read_view_sees_trx_id(view, trx_id));

}


read_view的创建过程


我们先关注一致性视图的创建过程,我们先看下read_view结构:



然后通过debug,发现创建read_view结构也是在上述的rr_sequential中操作的,继续跟踪调用栈:



我们看下row_search_for_mysql里的一个分支:


row_search_for_mysql:

// 这边只有select不加锁模式的时候才会创建一致性视图

else if (prebuilt->select_lock_type == LOCK_NONE) { // 创建一致性视图

trx_assign_read_view(trx);

prebuilt->sql_stat_start = FALSE;

}


上面的注释就是select for update(in share model)不会走MVCC的原因。让我们进一步分析trx_assign_read_view函数:



好了,终于到了创建read_view的主要阶段,主要过程如下图所示:



代码过程为:



行版本可见性:


由上面的lock_clust_rec_cons_read_sees可知,行版本可见性由read_view_sees_trx_id函数判断:



其实上述函数就是一个二分法,read_view其实保存的是当前活跃事务的所有事务id,如果当前行版本对应修改的事务id不在当前活跃事务里面的话,就返回true,表示当前版本可见,否则就是不可见,如下图所示。



接上述lock_clust_rec_cons_read_sees的返回:



undolog搜索可见版本的过程


我们现在考察一下row_sel_build_prev_vers_for_mysql函数:


row_sel_build_prev_vers_for_mysql

|-row_vers_build_for_consistent_read


主要是调用了row_ver_build_for_consistent_read方法返回可见版本:



整个过程如下图所示:



至于undolog怎么恢复出对应版本的row记录就又是一个复杂的过程了,由于篇幅原因,在此略过不表。


read_view创建时机再讨论


在创建一致性视图的row_search_for_mysql的代码中



trx_assign_read_view中由这么一段代码



所以综合这两段代码,即在一个事务中,只有第一次运行select(不加锁)的时候才会创建一致性视图,如下图所示:



笔者构造了此种场景模拟过,确实如此。


MVCC和锁的同时作用导致的一些现象


MySQL是通过MVCC和二阶段锁(2PL)来兼顾性能和一致性的,但是由于MySQL仅仅在select时候才创建一致性视图,而在update等加锁操作的时候并不做如此操作,所以就会产生一些诡异的现象。如下图所示:



如果理解了update不走一致性视图(read_view),而select走一致性视图(read_view),就可以很好解释这个现象。 如下图所示:



总结


MySQL为了兼顾性能和ACID使用了大量复杂的机制,2PL(两阶段锁)和MVCC就是其实现的典型。幸好可以通过xcode等IDE进行方便的debug,这样就可以非常精确加便捷的追踪其各种机制的实现。希望这篇文章能够帮助到喜欢研究MySQL源码的读者们。



【关于投稿】


如果大家有原创好文投稿,请直接给公号发送留言。


① 留言格式:
【投稿】+《 文章标题》+ 文章链接

② 示例:
【投稿】
《不要自称是程序员,我十多年的 IT 职场总结》:http://blog.jobbole.com/94148/


③ 最后请附上您的个人简介哈~




看完本文有收获?请转发分享给更多人

关注「数据分析与开发」,提升数据技能


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/h7gl8H6kOm
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/22541
 
685 次点击