自学内容网 自学内容网

多版本并发控制:MVCC的作用和基本原理

1、MVCC简介

1.1 快照读与当前读的区别

mysql在读数据的场景下,根据是否加锁分为了2种读的方式:

1.1.1 快照读

不加锁的简单的select都属于快照读,即不加锁的非阻塞读。比如:

SELECT * FROM students WHERE ...

之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC。他在很多情况下,避免了加锁操作,降低了开销。

1.1.2 当前读

当前读读取的是记录最新的数据。加锁的SELECT,或者对数据进行增删改查都会进行当前读。比如:

SELECT * FROM student LOCK IN SHARE MODE;  # 共享锁
SELECT * FROM student FOR UPDATE;  # 排他锁
INSERT INTO student VALUES ...;  # 排他锁
DELETE FROM student WHERE ...;  # 排他锁
UPDATE student SET ...;  # 排他锁

1.2 数据库的读写问题

数据库多事务场景下的读-读不会存在并发问题,写-写场景存在并发问题,因此一定需要加锁。

读-写、或者写-读场景下如何处理?

方案一:读操作也采用加锁的方式。
比如,在银行存款的事务中,需要先读取账户余额,然后再进行存/取操作,最后写入数据库中。在这个过程中,是不希望其他事务在该事务还未结束的情况下访问该余额。这种情况下,在读取余额的时候就需要进行加锁操作了。这样就可以保证本次操作结束后的金额,一定是和预期一致的。

1.3 MVCC的作用

数据库的读写问题,除了采用加锁的方式解决,还可以通过MVCC的方式解决。

MVCC(Multiversion Concurrency Control)多版本并发控制,通过数据行的多个版本管理实现数据库的并发控制。即查询一些正在被另一个事务更新的数据行时,可以看到他们被更新之前的值,不用等待另一个事务释放锁。

所谓的MVCC,就是生成一个ReadView,通过ReadView找到符合条件的记录版本(历史版本由undo日志构建)。查询语句只能读到在生成ReadView之前已提交事务做的修改,不影响其他事务进行写操作,因此可以解决读写问题。相比于加锁的方式解决读写问题,可以大幅提高并发性能。

2、MVCC实现原理之ReadView

MVCC的实现依赖于:隐藏字段、Undo Log、Read View

undo日志版本链中索引记录都包含2个必要的隐藏列:

  • trx_id:每次一个事务对某条索引记录进行修改时,都会把该事务的事务id赋值给trx_id隐藏列。
  • roll_pointer:每次对某条索引记录进行修改时,都会把旧版本写入到undo日志中,这个隐藏列相当于一个指针,可以通过它来找到该记录修改前的信息。

以下图举例,其中蓝色部分为页面的当前记录,绿色部分为undo日志。
在这里插入图片描述

2.1 什么是ReadView

多个事务对同一行记录进行更新会产品多个历史快照,这些历史快照保存在undo log里。如果一个事务想要查询这个行记录,怎么判断读取哪个版本的记录呢?这个时候就可以用上Readview了,它解决了多事务场景下的可见性的问题。

Readview就是事务在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,Innodb为每个事务构造了一个数组,用来记录并维护系统当前未提交事务的ID。

2.2 ReadView的设计思路

Readview要解决的核心问题是:判断版本链中的哪个版本是当前事务可见的。

为此,Readview设计了4个核心字段:

  1. creator_trx_id:创建Readview的事务ID。只有对表做增删改操作时才会分配事务ID,读操作的事务ID默认值为0。
  2. trx_ids:表示生成Readview时,当前系统中未提交事务的事务id列表。
  3. up_limit_id:当前未提交事务列表中最小的事务id。
  4. low_limit_id:生成Readview时,系统应该分配给下一个事务的id值。

举例:现在有id未1,2,3这3个事务,然后id为3的事务提交了。此时一个新的读事务在生成Readview时,trx_ids的值是:[1,2],up_limit_id的值是1,low_limit_id的值是4。

有了Readview,在访问某条记录时,根据以下步骤判断记录的某个版本是否可见

  1. 如果被访问版本的trx_id值与Readview中的creator_trx_id值相同:表明当前呢事务在访问自己修改的记录,所以该版本可以被当前事务访问。
  2. 如果被访问版本的trx_id值小于Readview中的up_limit_id值:表明生成该版本的事务在当前事务生成Readview之前就已经提交了,所以该版本可以被当前事务访问。
  3. 如果被访问版本的trx_id值大于等于Readview中的low_limit_id值:表明生成该版本的事务在当前事务生成Readview之后才开启,所以该版本不可以被当前事务访问。
  4. 如果被访问版本的trx_id值在Readview中的up_limit_id值和low_limit_id值之间,需要判断一下trx_id值是否在trx_ids列表中。
    1)如果在:说明创建Readview时,生成该版本的事务还未提交,该版本不可以被访问。
    2)如果不在:说明创建Readview时,生成该版本的事务已被提交,该版本可以被访问。

2.3 MVCC整体操作流程

查询记录时,MVCC操作流程如下。如果某个版本的数据对当前事务不可见的话,就顺着版本链找下一个版本的数据,继续按照流程进行判断。

  1. 获取事务自己的版本号,即事务ID
  2. 生成Readview
  3. 查询数据,根据数据的版本号(trx_id)值按照2.2中的规则进行判断
  4. 如果不符合2.2中Readview规则,从undo log中获取历史快照
  5. 循环执行,最后返回符合2.2中Readview规则中的数据。

原文地址:https://blog.csdn.net/xueping_wu/article/details/145132167

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!