最近在阅读<<认知觉醒>>这本书,里面有句话非常打动我:通过自己的语言,用最简单的话把一件事情讲清楚,最好让外行人也能听懂.
也许这就是大道至简,只是我们习惯了烦琐和复杂.
希望借助今天这篇文章,能用大白话说清楚这个相对比较底层和复杂的MVCC机制.
具体的问题见下图,我们设定有一张表user,初始化语句如下,试想在这样的场景下,事务A三次查询的值分别是什么?
create table +user+ ( +id+ bigint not null, +name+ varchar(50) default null, PROMARY KEY (+id+) ) ENGINE = InnoDB; insert into user(id,name) values (1,'A');
想要把这件事情回答正确,我们先来铺垫一下基础知识.
提到事务,首先会想到的就是ACID(Atomic原子性、Consist一致性、Isolate隔离性、Durable持久性),今天我们主要关注隔离性,当有多个事务同时执行发生并发时,数据库可能会出现脏读、不可重复读和幻读等问题,为了解决这些问题,"隔离级别"这位大哥上场,包含:读未提交、读已提交、可重复读和串行.
但我们都知道,隔离级别越高,执行效率越低.毕竟大哥就是大哥,级别越高,越谨慎,常在河边走哪能不湿鞋.
我们通过一个例子简单说一下这四种隔离级别:
MySQL是怎么实现的呢?我们以MySQL默认的可重复读隔离级别为例,实际上每条行记录在更新时都会记录一条回滚日志,也就是大家常说的undo log.通过回滚操作,都可以得到前一个状态的值.假设name值从初始值A被依次更新为B、C、D,我们看一下回滚日志:
到这里,你已经接触到了MVCC的概念,也许你已经对文章最开始的问题有了一点点想法,别着急,我们先来简单总结下MVCC的特点:
MVCC的出现使得一条行记录在不同隔离级别下不同的事务操作会形成一条不同版本的链路,从而实现在不加锁的前提下使不同事务的读写操作能够并发安全执行,这个版本链就是通过回滚日志undo log实现的.用大白话说,你这个事务想要查询一条行记录,MVCC会通过你这个事务所在视图确认版本链中哪个版本的行数据对你可见.刚才我们提到,四种隔离级别下,只有读已提交和可重复读会用到视图.对于读已提交,MVCC会在每次查询前都会生成一个视图,可重复读隔离级别只会在第一次查询时生成一个视图,之后在这个事务中的所有查询操作都会重复使用这个视图.行业上,将创建视图的那一刻称为快照,晃你一下子,让你激灵激灵,别发生脏读,变脏喽~
对于InnoDB存储引擎来说,主键索引(也称为聚簇索引)记录中除了正常的字段数据外,还包含两个隐藏列:
(1)trx_id:每次一个事务想要对主键索引进行更新、删除和新增时,都会把这个事务的事务id赋值给trx_id字段.注意事务id严格递增,且查询操作不会分配事务id,即trx_id = 0;
我们以可重复读隔离级别为例,为了尚未提交的更新结果对其他事务不可见,InnoDB在创建视图时,有以下四部分组成:
m_ids:表示生成视图时,当前系统中"活跃"的读写事务的事务id列表,这里的活跃大白话就是事务尚未提交;
min_trx_id:表示在生成视图时,当前系统中活跃的读写事务中最小的事务id,即m_ids中的最小值;
max_trx_id:表示生成视图时系统应该分配给下一个事务的id值;
creator_trx_id:表示生成该视图的事务id.
关键的知识点来了,如何根据某个事务生成的视图,判断版本链上的某个版本对这个事务可见呢?
遵循下面步骤:
①.、版本链上的不同版本trx_id值如果与这个视图的creator_trx_id值相同,说明当前事务在访问它自己修改过的记录,所以被访问的版本对当前事务可见.一家人还是认识一家人的~
比较绕是不是,千万别晕,兄弟呀~,大白话解释一下,设定某个事务生成的视图瞬间(也就是快照),这个事务的id为creator_trx_id,那么有下面三种可能:
①.、如果creator_trx_id落在绿色部分,表示被访问的版本是已提交的事务或者就是当前事务自己生成的,这个数据是可见的;
若creator_trx_id在m_ids集合中,表示被访问的版本尚未提交,数据不可见;
若creator_trx_id不在m_ids集合中,表示被访问的版本已经已经提交了,数据可见.
知道了这个之后,我们就可以回答文章最开始那个问题了,在隔离级别为可重复读的情况下(这里的隐含条件就是可重复读隔离级别只会在第一次查询时生成一个视图,之后在这个事务中的所有查询操作都会重复使用这个视图)分析一波:
这个时候我们看一下事务A第一个select语句,注意查询操作的事务trx_id=0,在执行select语句时会创建一个视图,这个视图的m_ids={100},min_trx_id=100,max_trx_id=101,creator_trx_id=0.
然后在版本链中挑选可见的数据记录,从图中可以看到最新版本的name值是B,最新版本的trx_id值为100,在m_ids集合中,这个版本数据不可见,根据roll_point跳到下一个版本;
此时此刻呢,事务C进行了更新操作,此时版本链发生的改变如下:
最后,我们再来最后提醒一下大家MVCC的作用,使用可重复读隔离级别的事务在查询时,仅会使用第一次select时生成的视图,相比于读已提交隔离级别每次查询都会生成一个新的视图,可重复读在查询时使用的视图版本不会那么新,所以呢有些已经提交的事务对行记录进行修改时对查询事务就不可见,进而避免了不可重复读现象的发生,同时也避免了脏读.
小问题答案:
读已提交隔离级别下,每次select查询都会生成一个新的视图,基于此,分析如下:
事务A第一个select语句,注意查询操作的事务trx_id=0,在执行select语句时会创建一个视图,这个视图的m_ids={100},min_trx_id=100,max_trx_id=101,creator_trx_id=0.
然后在版本链中挑选可见的数据记录,从图中可以看到最新版本的name值时B,最新版本的trx_id值为100,在m_ids集合中,这个版本数据不可见,根据roll_point跳到下一个版本;
以上就是土嘎嘎小编为大家整理的一文了解MySQL中的多版本并发控制相关主题介绍,如果您觉得小编更新的文章只要能对粉丝们有用,就是我们最大的鼓励和动力,不要忘记讲本站分享给您身边的朋友哦!!