文章来源我自己公众号:一个 MVCC 和面试官大战30回合
公众号很多硬核文章,求大家关注下呀~ 下面开始我们本篇文章。
我,小Y。
此刻,正坐在办公室里等待面试,心情xue微有点忐忑,不知道待会儿老面试官经不经得住我的折磨。
只见一抹光亮闪过,面试官推门而入,我抬头望去,强者的气息铺面而来,没错是那味儿。
看到面试官头上那“傲然矗立”的头发,脑海中止不住幻想他在无数个凌晨于电脑前挑灯夜码的高大形象,一种敬佩感油然而生, 竟忍不住站起来给他敬了个礼。
面试官:有病?
我:没没没,我谢顶反应综合征犯了,面试官好,我是小 Y ,请多多指教。
面试官:哦哦,确实是有病啊,没事,记得吃药就行。我看你简历写你 MySQL 挺懂的,那我先问问你 MySQL 吧。
我:好嘞,您请。
面试官:你知道什么是 MySQL 的酸吗?
这一来就这么猛的吗?脑海中一顿搜索,只能想起张含韵的我喜欢酸的甜这就是真的我之《酸酸甜甜就是我》,算了蒙一个。
我:事务?
面试官:哟,最近好多谐音梗,我特意玩了个英语单词短语梗,脑子转的挺快啊小伙子。
酸,英文 acid,说的就是事务!这都蒙对了,等下就去买彩票!趁这个机会再表现一下!
我:是啊,国外的人就有拼凑单词的习惯,其实事务主要是为了实现 C ,也就是一致性,具体是通过AID,即原子性、隔离性和持久性来达到一致性的目的,所以这四个不应该相提并论,但是他们就想拼成单词,就把它们排好序搞在一起来念。
嘿嘿,这个B装的我有点舒服,果然面试官有点惊讶。
面试官:可以呀,那你知道 MVCC 吧?
我:知道,Multi-Version Concurrency Control (多版本并发控制)。
面试官:能先简短的解释下什么是 MVCC 吗?
我:多版本并发控制,其实指的是一条记录会有多个版本,每次修改记录都会存储这条记录被修改之前的版本,多版本之间串联起来就形成了一条版本链,这样不同时刻启动的事务可以无锁地获得不同版本的数据(普通读)。此时读(普通读)写操作不会阻塞,写操作可以继续写,无非就是多加了一个版本,历史版本记录可供已经启动的事务读取。
(为保持简短,简化了SQL语句,下文也同样简化)
面试官:那你知道事务四种隔离级别吧?
我:读未提交、读已提交、可重复读、可串行化。
面试官:MVCC 用来实现哪几个隔离级别?
我:用来实现读已提交和可重复读。首先隔离级别如果是读未提交的话,直接读最新版本的数据就行了,压根就不需要保存以前的版本。可串行化隔离级别事务都串行执行了,所以也不需要多版本,因此 MVCC 是用来实现读已提交和可重复读的。
面试官:那为什么需要 MVCC ?如果没有 MVCC 会怎样?
我:如果没有 MVCC 读写操作之间就会冲突。想象一下有一个事务1正在执行,此时一个事务2修改了记录A,还未提交,此时事务1要读取记录A,因为事务2还未提交,所以事务1无法读取最新的记录A,不然就是发生脏读的情况,所以应该读记录A被事务2修改之前的数据,但是记录A已经被事务2改了呀,所以事务1咋办?只能用锁阻塞等待事务2的提交,这种实现叫 LBCC(Lock-Based Concurrent Control)。
如果有多版本的话,就不一样了。事务2修改的记录 A,还未提交,但是记录 A 被修改之前的版本还在,此时事务1就可以读取之前的版本数据,这样读写之间就不会阻塞啦,所以说 MVCC 提高了事务的并发度,提升数据库的性能。
面试官:你对这个多版本有没有什么别的理解?
我:(面试官要开始操作我了吗?不过就这,我早有准备!)有点个人的小理解(假装谦虚)。其实这个多版本不是很准确,只是为了便于理解或者说展现出来像多版本的样子而已。
实际上 InnoDB 不会真的存储了多个版本的数据,只是借助 undolog 记录每次写操作的反向操作,所以索引上对应的记录只会有一个版本,即最新版本。只不过可以根据 undolog 中的记录反向操作得到数据的历史版本,所以看起来是多个版本。
面试官:那你能详细的说下 MVCC 是如何实现的吗?
我:您听好啦。
拿上面的 insert (1,XX)
这条语句举例,成功插入之后数据页的记录上不仅存储 ID 1,name XX,还有 trx_id 和 roll_pointer 这两个隐藏字段:
- trx_id:当前事务ID。
- roll_pointer:指向 undo log 的指针。
从图中可以得知此时插入的事务ID是1,此时插入会生成一条 undolog ,并且记录上的 roll_pointer 会指向这条 undolog ,而这条 undolog 是一个类型为TRX_UNDO_INSERT_REC
的 log,代表是 insert 生成的,里面存储了主键的长度和值(还有其他值,不提),所以 InnoDB 可以根据 undolog 里的主键的值,找到这条记录,然后把它删除来实现回滚(复原)的效果。因此可以简单地理解 undolog 里面存储的就是当前操作的反向操作,所以认为里面存了个 delete 1
就行。
此时事务1提交,然后另一个 ID 为 5 的事务再执行 update NO where id 1
这个语句,此时的记录和 undolog 就如下图所示:
没错,之前 insert 产生的 undolog 没了,insert 的事务提交了之后对应的 undolog 就回收了,因为不可能有别的事务会访问比这还要早的版本了,访问插入之前的版本?访问
个寂寞吗?
而 update 产生的 undolog 不一样,它的类型为 TRX_UNDO_UPD_EXIST_REC
。
此时事务 5 提交,然后另一个 ID 为 11 的事务执行update Yes where id 1
这个语句,此时的记录和 undolog 就如下图所示: