MVCC理解

MySQL的**MVCC(Multi-Version Concurrency Control,多版本并发控制)**是一种高效的并发控制机制,通过维护数据的多个版本实现读写操作的并行执行,显著提升数据库的并发性能和数据一致性。

MVCC 的实现依赖于:隐藏字段、Read View、undo log

隐藏字段

  • DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务 id。 此外,delete 操作在内部被视为更新,只不过会在记录头 Record header 中的 deleted_flag 字段将其标记为已删除
  • **DB_ROLL_PTR(7字节): 回滚指针,指向该行的 undo log 。**如果该行未被更新,则为空
  • DB_ROW_ID(6字节):如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该 id 来生成聚簇索引

Read View

ReadView(读视图)是 InnoDB 为了实现一致性读(Consistent Read)而创建的数据结构,它用于确定在特定事务中哪些版本的行记录是可见的。

当事务开始执行时,InnoDB 会为该事务创建一个 ReadView,这个 ReadView 会记录 4 个重要的信息:

  • **creator_trx_id:**创建该 ReadView 的事务 ID。

  • **m_ids:**所有活跃事务的 ID 列表,活跃事务是指那些已经开始但尚未提交的事务。

  • **min_trx_id:**所有活跃事务中最小的事务 ID。它是 m_ids 数组中最小的事务 ID。

  • **max_trx_id:**事务 ID 的最大值加一。换句话说,它是下一个将要生成的事务 ID。

Undo log

undo log(回滚日志) 主要有两个作用:

  • 当事务回滚时用于将数据恢复到修改前的样子
  • 另一个作用是 MVCC ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 undo log 读取之前的版本数据,以此实现非锁定读

InnoDB 存储引擎中 undo log 分为两种:insert undo logupdate undo log

  1. insert undo log :指在 insert 操作中产生的 undo log。因为 insert 操作的记录只对事务本身可见,对其他事务不可见,故该 undo log 可以在事务提交后直接删除。不需要进行 purge 操作
  2. update undo logupdatedelete 操作中产生的 undo log。该 undo log可能需要提供 MVCC 机制,因此不能在事务提交时就进行删除。提交时放入 undo log 链表,等待 purge线程 进行最后的删除

可见性判断

当读取一行数据时,会通过以下规则判断该版本是否可见:

  • 如果该版本的DB_TRX_ID小于min_trx_id,说明该版本在创建ReadView时已经提交,可见
  • 如果该版本的DB_TRX_ID大于等于max_trx_id,说明该版本在创建ReadView时还未开始,不可见
  • 如果该版本的DB_TRX_ID在m_ids中,说明该版本在创建ReadView时还未提交,不可见
  • 如果该版本的DB_TRX_ID等于creator_trx_id,说明该版本是当前事务修改的,可见

可见性判断举例

前景知识

事务开始和ReadView创建的时序关系:

  1. 事务开始
    1. 当执行 BEGIN 或 START TRANSACTION 时,事务开始
    2. 此时会分配一个事务ID(trx_id)
    3. 但此时并不会创建ReadView
  2. ReadView创建
    1. ​​​​​ 在第一次执行SELECT语句时才会创建ReadView
    2. 不同隔离级别下,ReadView的创建时机不同:
      • **READ COMMITTED:**每次SELECT都会创建新的ReadView
      • **REPEATABLE READ:**第一次SELECT时创建ReadView,后续复用

假设我们有一个用户表,包含以下数据:

sql 复制代码
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT
);

INSERT INTO users VALUES (1, '张三', 20);

场景1:事务ID小于min_trx_id(已提交事务)

sql 复制代码
事务1 (trx_id=100): 开始事务
事务2 (trx_id=101): 开始事务
事务1: 更新 users 表,将张三的年龄改为21
事务1: 提交事务
事务2: 创建ReadView (min_trx_id=101, max_trx_id=102, m_ids=[101])
事务2: 读取 users 表

结果:事务2可以看到更新后的数据(年龄=21)

原因:因为事务1的trx_id(100) < min_trx_id(101),说明该版本在创建ReadView时已经提交

场景2:事务ID大于等于max_trx_id(未开始事务)

sql 复制代码
事务1 (trx_id=100): 开始事务
事务1: 创建ReadView (min_trx_id=100, max_trx_id=101, m_ids=[100])
事务2 (trx_id=101): 开始事务
事务2: 更新 users 表,将张三的年龄改为22
事务1: 读取 users 表

结果:事务1看不到事务2的更新(仍然看到年龄=20)

原因:因为事务2的trx_id(101) >= max_trx_id(101),说明该版本在创建ReadView时还未开始

场景3:事务ID在m_ids中(未提交事务)

sql 复制代码
事务1 (trx_id=100): 开始事务
事务2 (trx_id=101): 开始事务
事务1: 更新 users 表,将张三的年龄改为21
事务2: 创建ReadView (min_trx_id=100, max_trx_id=102, m_ids=[100,101])
事务2: 读取 users 表

结果:事务2看不到事务1的更新(仍然看到年龄=20)

原因:因为事务1的trx_id(100)在m_ids中,说明该版本在创建ReadView时还未提交

场景4:事务ID等于creator_trx_id(当前事务修改)

sql 复制代码
事务1 (trx_id=100): 开始事务
事务1: 创建ReadView (min_trx_id=100, max_trx_id=101, m_ids=[100])
事务1: 更新 users 表,将张三的年龄改为21
事务1: 读取 users 表

结果:事务1可以看到自己的更新(看到年龄=21)

原因:因为该版本的trx_id(100)等于creator_trx_id(100),说明该版本是当前事务修改的

隔离级别与MVCC

​1. 读未提交(Read Uncommitted)​

  • ​MVCC 行为​​不使用 MVCC​,直接读取数据的最新版本(包括未提交的数据)。
  • ​数据可见性​:事务可看到其他事务未提交的修改(脏读)。
  • ​典型问题​:脏读、不可重复读、幻读均可能发生。
  • ​适用场景​:对数据一致性要求极低的场景(如日志记录)。

​2. 读已提交(Read Committed)​

  • ​MVCC 行为​
    • ​每次 SELECT 生成新 ReadView​:每次查询时创建新的快照,仅读取已提交的数据版本。
    • ​写操作使用当前读​:UPDATE/DELETE 操作会读取最新已提交数据并加锁。
  • ​数据可见性​:事务内多次查询可能看到不同结果(因其他事务提交导致数据变更)。
  • ​解决的问题​:脏读(因只读已提交数据)。
  • ​未解决的问题​:不可重复读、幻读(因无间隙锁)。
  • ​适用场景​:需避免脏读,但允许不可重复读的场景(如实时统计)。

​3. 可重复读(Repeatable Read,MySQL 默认级别)​

  • ​MVCC 行为​
    • ​事务开始时生成固定 ReadView​:整个事务复用同一快照,确保多次查询结果一致。
    • ​通过版本链访问历史数据​ :通过 DB_ROLL_PTR 回溯旧版本。
  • ​锁机制补充​​间隙锁(Gap Lock)​ 阻止范围内新数据插入,解决幻读。
  • ​解决的问题​:脏读、不可重复读、幻读(InnoDB 通过 MVCC + 间隙锁实现)。
  • ​适用场景​:需保证事务内数据一致性的场景(如账户余额查询)。

4. 串行化(Serializable)​

  • ​MVCC 行为​​基本不使用 MVCC​,主要依赖严格的锁机制(读写均加锁)。
  • ​数据可见性​:事务串行执行,完全隔离并发操作。
  • ​解决的问题​:所有并发问题(脏读、不可重复读、幻读)。
  • ​缺点​​性能最低​,高并发下易引发锁等待和死锁。
  • ​适用场景​:对数据一致性要求极高且并发量低的场景(如金融清算)。
  • **读未提交(Read Uncommitted):不使用MVCC,**直接读取数据的最新版本(包括未提交的数据),脏读、不可重复读、幻读均可能发生。
  • 读已提交(Read Committed)​:使用MVCC
  • 可重复读(Repeatable Read,MySQL 默认级别)​
  • 串行化(Serializable)​
相关推荐
一叶知秋哈19 分钟前
Java应用Flink CDC监听MySQL数据变动内容输出到控制台
java·mysql·flink
pp-周子晗(努力赶上课程进度版)2 小时前
【MySQL】视图、用户管理、MySQL使用C\C++连接
数据库·mysql
超级小忍3 小时前
如何配置 MySQL 允许远程连接
数据库·mysql·adb
吹牛不交税3 小时前
sqlsugar WhereIF条件的大于等于和等于查出来的坑
数据库·mysql
Java水解4 小时前
MySQL DQL全面解析:从入门到精通
后端·mysql
HyggeBest7 小时前
Mysql 宏观架构
mysql·架构
jiunian_cn8 小时前
【Linux】Linux权限
linux·服务器·mysql
betazhou8 小时前
有没有 MariaDB 5.5.56 对应 MySQL CONNECTION_CONTROL 插件
linux·数据库·mysql·oracle·mariadb