【笔记】MySQL 中的 MVCC

98 阅读2分钟

SQL 规定的四种隔离级别

隔离级别 / 问题脏读不可重复读幻读
READ UNCOMMITTEDpossiblepossiblepossible
READ COMMITTEDNot possiblepossiblepossible
REPEATABLE READNot possibleNot possiblepossible
SERIALIZABLENot possibleNot possibleNot possible

备注: mysql 支持所有四种隔离级别,但与表中描述不同的是,InnoDB 在 repeatable 隔离级别下,能够禁止幻读问题的发生。

mysql 中的隔离级别

  • 设置隔离级别
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL level;
  • 查看隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';

SELECT @@transaction_isolation;

原理

版本链

InnoDB 的聚簇索引记录中包含两个必要的隐藏列

  • trx_id: 一个事务对某条聚簇索引进行改动时,都会把该事务的 id 赋予该列
  • roll_pointer: 每次对某条聚簇索引记录进行改动时,都会把旧的记录写入到 undo log 中,该隐藏列相当于一个指针,可以通过 roll_pointer 找到该记录的历史版本
CREATE TABLE hero(
    id BIGINT NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(20),
    PRIMARY KEY `id`
)engine=InnoDB;

INSERT INTO hero VALUES(1, '刘备');

假设插入第一条记录的事务 id 为 80,那插入完成后,该记录的示意图如下: 插入记录

假设插入之后,事务 id 分别为 100 和 200 的两个事务都来更新该记录: 更新操作

形成了一个版本链: 版本链

read view

  • 对于隔离级别为 READ UNCOMMITTED 的事务来说,每次读取时,直接取最新版本数据即可
  • 对于 SERIALIZABLE 隔离级别来说,InnoDB 规定使用加锁的方式来访问记录
  • 针对 READ COMMITTED 和 REPEATABLE READ 隔离级别而言,就需要借助 read view 来判断版本链中的哪个版本是当前事务可见的

read view 四个重要属性

  • m_ids: 在生成 read_view 时 当前系统处于活跃的读写事务的 trx_id 集合
  • min_trx_id: 表示生成 read view 时当前系统处于活跃的读写事务中的最小 trx_id,也就是 m_ids 中的最小值
  • max_trx_id: 表示生成 read view 时系统应当分配的下一个 trx_id
  • creator_trx_id: 表示生成该 read view 的事务 id

read view 生成后,便利版本链,假设当前版本 trx_id 记作 cur_trx_id,按照以下步骤判断某个版本是否可见:

  • 如果 cur_trx_id == creator_trx_id,则可见
  • 如果 cur_trx_id < min_trx_id,则可见
  • 如果 cur_trx_id >= max_trx_id,则不可见
  • 如果 min_trx_id < cur_trx_id < max_trx_id,判断 cur_trx_id 是否在 m_ids 集合中
    • 如果 cur_trx_id 在 m_ids 集合中,则表示创建 read view 时生成该版本的事务是活跃的,不可以访问该版本
    • 如果 cur_trx_id 不再 m_ids 集合中,则表示创建 read view 时,生成该版本的事务已经提交,可以访问该版本

mysql 中,READ COMMITTED 和 REPEATABLE 两种隔离级别的区别就在于生成 read view 的时机不同

  • READ COMMITTED 在每次读取数据前都生成一个 read view
  • REPEATABLE 在第一次读取数据时生成一个 read view,之后事务过程中不再生成新的 read view