MySQL-MVCC-深入解析

本文深入解析MySQL的MVCC机制,探讨版本链、ReadView的工作原理,以及其在提交读和可重复读隔离级别下的应用,阐述MVCC如何提高并发性能并防止脏读、不可重复读等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、什么是MVCC?

MVCC,Multi-Version Concurrency Control,多版本并发控制,是MySQL的默认存储引擎InnoDB实现隔离级别的一种具体方式,能够实现提交读/READ-COMMITTED和可重复读/REPEATABLE-READ两种隔离级别。
MVCC指的就是在使用 READ COMMITTDREPEATABLE READ这两种隔离级别的事务在执行普通的 SELECT 操作时,访问记录的版本链的过程。可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。

  • 提交读/READ-COMMITTED:允许读取并发事务已经提交的数据, 可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • 可重复读/REPEATABLE-READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

2、版本链和ReadView

(1)版本链

对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列:

  • trx_id:一个是事务ID,保存修改此条记录的事务ID
  • roll_pointer:一个是回滚指针,指向该记录修改前的信息

(2)ReadView

  • 对使用READ UNCOMMITTED隔离级别的事务,直接读取记录的最新版
  • 对使用SERIALIAZABLE隔离级别的事务,使用加锁来读取记录
  • 对使用READ-COMMITTED和REPEATABLE-READ隔离级别的事务,需要使用版本链读记录
    核心问题:判断版本链中的哪个版本是当前事务可见的?
    -----------------------------------

ReadView的四个主要内容:

  • m_ids:表示在生成 ReadView 时,当前系统中活跃的读写事务的事务 id 列表。
  • min_trx_id:表示在生成 ReadView 时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小 值。
  • ****:表示生成 ReadView 时系统中应该分配给下一个事务的id值。
  • creator_trx_id:表示生成该 ReadView 的事务的事务id。
    ----------------------------------
    READ COMMITED 在每一次进行普通 SELECT 操作前都会生成一个ReadView,而 REPEATABLE READ 只在 第一次进行普通 SELECT 操作前生成一个 ReadView ,之后的查询操作都重复使用这个 ReadView。
    ----------------------------------
    通过比较版本链中被访问版本的trx_id和ReadView中的creator_trx_id /min_trx_id/max_trx_id来判断当前记录是否可以被此事务访问
    1、trx_id == creator_trx_id相同,则当前事务在访问它自己修改过的记录,该版本可以被当前事务访问;
    2、trx_id < min_trx_id,表明生成该版本的事务在当前事务生成ReadView之前已经提交了,该版本可以被当前事务访问;
    3、trx_id > max_trx_id,证明生成该版本的事务,在当前事务生成ReadView后才开启,该版本不能被当前事务访问;
    4、 min_trx_id < trx_id < max_trx_id,需要继续判断 trx_id是否在m_ids列表中
    1)在,说明创建ReadView时生成该版本的事务还是活跃的,不能被访问
    2)不在,说明创建ReadView时生成该版本的事务已提交,该版本能被访问

3、MVCC带来的好处

-数据多版本(MVCC)是MySQL实现高性能的一个主要的方式,通过对普通的SELECT不加锁,直接利用MVCC读取指定版本的值,避免了对数据重复加锁的过程.

  • 在 READ COMMITED 事务隔离级别下,对于快照数据,读取被锁定行的最新一份快照数据
  • 在 REPEATABLE READ事务隔离级别下,读取事务开始时的行数据版本

在实际场景中读操作往往多于写操作,而 MVCC 利用了多版本的思想,写操作更新最新的版本快照,而读操作去读旧版本快照,没有互斥关系。

  • 在 MVCC 中事务的修改操作(DELETE、INSERT、UPDATE)会为数据行新增一个版本快照。
  • 可以认为MVCC是行级锁的一个变种,InnoDB采用了乐观锁的策略,在每行记录保存两个隐藏列来实现,这两个列保存了行的版本号信息,每开启一个新事务,版本号自动更新,事务开始时刻的版本号作为事务的版本号。用来和查询到的记录所带的版本号进行比较来判断。
    在事务进行读取操作时,为了解决脏读和不可重复读问题,MVCC 规定只能读取已经提交的快照。当然一个事务可以读取自身未提交的快照,这不算是脏读。
    系统版本号 SYS_ID:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。
    事务版本号 TRX_ID :事务开始时的系统版本号。

4、Undo日志

MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过2.1介绍的回滚指针 ROLL_PTR 把一个数据行的所有快照连接起来。

  • 例如在 MySQL 创建一个表 t,包含主键 id 和一个字段 x。我们先插入一个数据行,然后对该数据行执行两次更新操作。
INSERT INTO t(id, x) VALUES(1, "a");
UPDATE t SET x="b" WHERE id=1;
UPDATE t SET x="c" WHERE id=1;

因为没有使用 START TRANSACTION 将上面的操作当成一个事务来执行,根据 MySQL 的 AUTOCOMMIT 机制,每个操作都会被当成一个事务来执行,所以上面的操作总共涉及到三个事务。快照中除了记录事务版本号 TRX_ID 和操作之外,还记录了一个 bit 的 DEL 字段,用于标记是否被删除。


INSERT、UPDATE、DELETE 操作会创建一个日志,并将事务版本号 TRX_ID 写入。DELETE 可以看成是一个特殊的 UPDATE,还会额外将 DEL 字段设置为 1。

5、ReadView的结构

MVCC 维护了一个 ReadView 结构,主要包含了当前系统未提交的事务列表 TRX_IDs {TRX_ID_1, TRX_ID_2, …},还有该列表的最小值 TRX_ID_MIN 和 TRX_ID_MAX。


在进行 SELECT 操作时,根据数据行快照的 TRX_ID 与 TRX_ID_MIN 和 TRX_ID_MAX 之间的关系,从而判断数据行快照是否可以使用:

  • TRX_ID < TRX_ID_MIN,表示该数据行快照时在当前所有未提交事务之前进行更改的,因此可以使用。

  • TRX_ID > TRX_ID_MAX,表示该数据行快照是在事务启动之后被更改的,因此不可使用。

  • TRX_ID_MIN <= TRX_ID <= TRX_ID_MAX,需要根据隔离级别再进行判断:

    • 提交读:如果 TRX_ID 在 TRX_IDs 列表中,表示该数据行快照对应的事务还未提交,则该快照不可使用。否则表示已经提交,可以使用。
    • 可重复读:都不可以使用。因为如果可以使用的话,那么其它事务也可以读到这个数据行快照并进行修改,那么当前事务再去读这个数据行得到的值就会发生改变,也就是出现了不可重复读问题。

在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。

6、快照读和当前读### 1. 快照读

(1)快照读

MVCC 的 SELECT 操作是快照中的数据,不需要进行加锁操作。

SELECT * FROM table ...;

(2)当前读

MVCC 其它会对数据库进行修改的操作(INSERT、UPDATE、DELETE)需要进行加锁操作,从而读取最新的数据。可以看到 MVCC 并不是完全不用加锁,而只是避免了 SELECT 的加锁操作。

INSERT;
UPDATE;
DELETE;

在进行 SELECT 操作时,可以强制指定进行加锁操作。以下第一个语句需要加 S 锁,第二个需要加 X 锁。

SELECT * FROM table WHERE ? lock in share mode;
SELECT * FROM table WHERE ? for update;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值