MVCC理解

发布于:2025-06-05 ⋅ 阅读:(22) ⋅ 点赞:(0)

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,后续复用

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

CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    age INT
);

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

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

事务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(未开始事务)

事务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中(未提交事务)

事务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(当前事务修改)

事务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)​


网站公告

今日签到

点亮在社区的每一天
去签到