MVCC和快照读

MVCC和快照读

首先我们回忆一下事务的隔离级别MySQL的事务和隔离级别实战,在RR(Repeatable Read,可重复读)中每一次读的都是事务启动时保存的快照,这样才能不管其他事务如何更新,都不影响当前事务读取的结果。那么这里就引入了一个重要的概念——快照。

快照和版本链

在InnoDB中每条记录会额外添加DB_TRX_IDDB_ROLL_PTR两列。

  • DB_TRX_ID:一个事务每次对某条主键索引记录进行改动时,会把该事务的事务ID赋值给DB_TRX_ID

  • DB_ROLL_PTR:每次改动时会把记录的旧版本写入到undo log,这个隐藏列相当于一个指针,它指向对应的undo log,通过DB_ROLL_PTR我们就可以回溯该记录的历史修改。

现在我们可以理解,undo log就相当于快照DB_ROLL_PTR可以将这些undo log串成一个链表,这个链表就是版本链

MVCC

利用版本链我们就可以控制不同事务访问同一条记录时,应该用哪一个版本,这就是多版本并发控制(Multi-Version Concurrency Control, MVCC)。那么,该记录的哪一个版本是当前事务可见呢?这就又引出了一个重要的概念——一致性视图(ReadView)。在一致性视图里有4个比较重要的内容:

活跃:启动了但还没有提交

  • m_ids:生成一致性视图时,当前系统活跃的读写事务的事务ID列表

  • min_trx_id:生成一致性视图时,当前系统中活跃的读写事务中最小的事务ID,也就是m_ids中的最小值。

  • max_trx_id:在生成一致性视图时,系统应该分配给下一个事务的事务ID值。计算方式就是当前系统里面已经创建过的事务ID的最大值加1。

  • creator_trx_id:生成该一致性视图的事务的事务ID(只有对表中的记录进行改动时,即insert、delete、update操作,才会为事务分配唯一的事务ID,否则一个事务的事务ID默认为0)。

现在,在访问某条记录时,只需要按照下面的步骤来判断记录的某个版本是否可见:

  1. 如果该版本的DB_TRX_ID < min_trx_id,代表该版本对应的事务在当前事务生成一致性视图前已经提交,所以该版本可以被当前事务访问。

  2. 如果该版本的DB_TRX_ID > max_trx_id,代表该版本对应的事务在当前事务生成一致性视图后才开启,所以该版本不可以被当前事务访问。

  3. 如果该版本的DB_TRX_ID∈[min_trx_id, max_trx_id],则需要判断DB_TRX_ID是否在m_ids中。如果在,说明当前事务在生成一致性视图时,该版本对应的事务仍处于活跃状态,那么该版本不可以被当前事务访问;如果不在,说明当前事务在生成一致性视图时,该版本对应的事务已经被提交,该版本可以被当前事务访问。

大家可以暂停阅读思考一下,如果不能被当前事务访问,应该怎么办?

答案是:顺着版本链往下找到下一个版本的数据,并利用上面的步骤判断记录的可见性,直至找到可见版本或者返回空。

需要注意的是,不同时机生成的一致性视图里面的内容是不一样的,所以开启一个事务后,理解该事务到底选择的是什么时机生成的一致性视图是十分重要的。在MySQL中,最常用的读已提交(Read Committed, RC)和可重复读(Read Repeatable, RR)两种事务隔离级别根本区别就在于生成一致性视图的时机不同。下面我们以该表为例说明两种隔离级别的区别:

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c` (`c`)
) ENGINE=InnoDB;

insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);

可重复读

事务A事务B事务C
begin;begin;
update t set c = 1 where id = 0;update t1(另一个表) set c =1 where id = 0;
update t set c = 2 where id = 0;
begin;
【查询1】select * from t where id = 0;

在事务B中更新另一个表的数据主要是为了产生一个事务ID。还记得吗?上面我们提到只有对表中的记录进行改动时,即insert、delete、update操作,才会为事务分配唯一的事务ID,否则一个事务的事务ID默认为0。假设事务A的事务ID为10,事务B的事务ID为20,则事务C的【查询1】过程如下:

  1. 在执行select时会产生一个一致性视图,其m_ids的内容就是[10,20],creator_trx_id为0。

  2. 从上图可以看出,c的最新版本为2,DB_TRX_ID的值为10,在m_ids中,因此不满足可见条件,需要顺着DB_ROLL_PTR往下找。

  3. 下一个版本的DB_TRX_ID的值还是为10,继续往下寻找。

  4. 最后找到DB_TRX_ID值为5的版本,满足可见条件,返回该记录,c=0。

事务A事务B事务C
commit;
update t set c = 3 where id = 1;
update t set c = 4 where id = 1;
【查询2】select * from t where id = 0;

接下来,我们将事务A提交,然后尝试在事务B中更新t表id=1这条记录。此时得到的版本链如下图所示:

事务C的【查询2】执行过程如下:

  1. 在RR级别下,只有第一次select会产生一致性视图,所以m_ids的内容还是[10,20]。

  2. 从上图可以看出,c的最新版本为4,DB_TRX_ID的值为20,在m_ids中,因此不满足可见条件,需要顺着DB_ROLL_PTR往下找。

  3. 最后找到DB_TRX_ID值为5的版本,满足可见条件,返回该记录,c=0。

可以看出来,在RR级别下,【查询1】、【查询2】得到的结果是一样的,这就是可重复读的含义。

读已提交

我们完全可以复用上述例子来分析RC级别下【查询1】、【查询2】的结果。首先可以确定的是,【查询1】RC和RR的读取结果是一样的。但是,在【查询2】的时候,RC会再生成一次一致性视图,这样RC级别下【查询2】的执行过程如下:

  1. 此时事务A已提交,重新生成一致性视图时,m_ids的内容是[20]。

  2. 从之前的图可以看出,c的最新版本为4,DB_TRX_ID的值为20,在m_ids中,因此不满足可见条件,需要顺着DB_ROLL_PTR往下找。

  3. 但是在找到DB_TRX_ID的值为10的记录时,发现10不在m_ids中,则该版本记录是可见的。所以停止寻找,返回该记录,c=2。

可以看出来,在RC级别下,【查询1】、【查询2】得到的结果是不一样的,事务C会读取已经提交事务更新后的结果,这就是读已提交的含义。

快照删除

  • 对于insert产生的undo log,在该事务提交之后就可以删除了

  • 对于update产生的undo log,比如上面例子中的事务A产生的undo log,在事务A提交后不能立即删除,因为事务C在【查询2】的执行过程中还会访问事务A产生的undo log。

    什么时候可以删除update产生的undo log?

    【简单理解】当系统中最早产生的那个一致性视图不再需要访问它们,即m_ids中没有或者小于min_trx_id,那么这些update undo log就可以删除了。
    【详细分析】事务提交时,会给事务生成一个名为事务 no的值,先提交的事务的事务 no更小。一致性视图中也有事务 no这个属性,生成一致性视图的时候,把当前系统中最大的事务 no,加1赋值给这个属性。执行purge时把系统中最早的一致性视图取出来,然后从各个回滚段的History链表中取出事务no较小的各组undo log,如果这组undo log的事务 no值小于最早一致性视图的事务 no值,那么就可以彻底删除释放了。

以上面的例子为例,在事务A提交前,产生的最早一致性视图是事务C查询得到的,保存的事务 no假设为101+1=102,事务A提交后对应undo log的事务 no也是102,所以只有当事务C提交了之后,最早的一致性视图的事务 no才可能大于102,此时事务A对应的undo log才可以彻底删除。


如今互联网上各类文章满天飞,但是大部分要不是寥寥数语,让人过目即忘;要不是过多细枝末节又没有实操,让人不知所云。我将从个人学习和工作经历出发,给大家带来深入浅出的技术解析。我的文章力求简短精悍,尽量结合实战,以便大家在碎片时间即可充分吸收,后续还能学以致用。

欢迎大家关注我的微信公众号,所有文章第一时间更新~

  • 28
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值