MySQL MVCC 机制详解

发布于:2025-04-14 ⋅ 阅读:(27) ⋅ 点赞:(0)

MySQL MVCC 机制详解

1. MVCC 基本概念

MVCC 是一种并发控制的方法,主要用于数据库管理系统,允许多个事务同时读取数据库中的同一个数据项,而不需要加锁,从而提高了数据库的并发性能。

┌─────────────────────────────────────┐
│         MVCC 的核心思想              │
│                                     │
│  对数据进行修改操作时,不会直接覆盖数据,│
│  而是创建一个新版本,让读操作可以看到   │
│  修改前的数据                        │
└─────────────────────────────────────┘

2. MVCC 在 InnoDB 中的实现

InnoDB 引擎下 MVCC 的实现主要基于以下几个关键概念:

2.1 隐藏字段

InnoDB 为每一行记录添加了三个隐藏字段:

┌───────────────────────────────────────────────────────┐
│                   InnoDB 行记录结构                     │
├───────────┬───────────┬───────────┬───────────────────┤
│ DB_TRX_ID │ DB_ROLL_PTR│ DB_ROW_ID │ 实际数据列(可见部分) │
│ 事务ID    │ 回滚指针   │ 行ID(可选) │                    │
└───────────┴───────────┴───────────┴───────────────────┘
  • DB_TRX_ID:创建或最后修改该记录的事务ID
  • DB_ROLL_PTR:指向 undo log 的指针,用于数据回滚
  • DB_ROW_ID:如果没有主键,InnoDB 会自动生成的行ID

2.2 undo log 版本链

当一行记录被修改时,InnoDB 会将旧版本的记录写入 undo log,并在当前记录中通过回滚指针指向这个 undo log 记录,形成一个版本链。

┌──────────────────────────────────────────────────────────────┐
│                       版本链示意图                            │
│                                                              │
│  最新记录                                                     │
│  ┌─────────┬─────────┬─────────┬───────────┐                 │
│  │TRX_ID=30│ROLL_PTR │ROW_ID   │name="张三" │                 │
│  └─────────┴─────────┴─────────┴───────────┘                 │
│      │                                                       │
│      ▼ 回滚指针指向                                           │
│  Undo Log 1                                                  │
│  ┌─────────┬─────────┬─────────┬───────────┐                 │
│  │TRX_ID=20│ROLL_PTR │ROW_ID   │name="李四" │                 │
│  └─────────┴─────────┴─────────┴───────────┘                 │
│      │                                                       │
│      ▼ 回滚指针指向                                           │
│  Undo Log 2                                                  │
│  ┌─────────┬─────────┬─────────┬───────────┐                 │
│  │TRX_ID=10│ROLL_PTR │ROW_ID   │name="王五" │                 │
│  └─────────┴─────────┴─────────┴───────────┘                 │
│                                                              │
└──────────────────────────────────────────────────────────────┘

2.3 ReadView

ReadView 是 MVCC 实现的关键机制,它决定了当前事务能够看到哪个版本的数据。ReadView 包含以下重要信息:

  • m_ids:当前系统中活跃的事务ID集合
  • min_trx_id:活跃的最小事务ID
  • max_trx_id:系统中将要分配给下一个事务的ID
  • creator_trx_id:创建该 ReadView 的事务ID
┌──────────────────────────────────────────────┐
│               ReadView 示意图                 │
│                                              │
│  ┌────────────────────────────────────────┐  │
│  │ m_ids: [10, 20, 30]                    │  │
│  │ min_trx_id: 10                         │  │
│  │ max_trx_id: 40                         │  │
│  │ creator_trx_id: 25                     │  │
│  └────────────────────────────────────────┘  │
│                                              │
└──────────────────────────────────────────────┘

3. MVCC 可见性判断规则

当一个事务要读取一行记录时,它会根据 ReadView 和记录的 DB_TRX_ID 来判断该版本的记录是否可见:

┌─────────────────────────────────────────────────────────────────┐
│                  MVCC 可见性判断流程图                           │
│                                                                 │
│   ┌───────────────┐                                             │
│   │ 开始判断可见性 │                                             │
│   └───────┬───────┘                                             │
│           ▼                                                     │
│   ┌───────────────────────────────┐     是     ┌───────────┐     │
│   │ trx_id == creator_trx_id?    ├────────────►│ 可见     │     │
│   └───────────┬───────────────────┘            └───────────┘     │
│               │ 否                                               │
│               ▼                                                 │
│   ┌───────────────────────────────┐     是     ┌───────────┐     │
│   │ trx_id < min_trx_id?         ├────────────►│ 可见     │     │
│   └───────────┬───────────────────┘            └───────────┘     │
│               │ 否                                               │
│               ▼                                                 │
│   ┌───────────────────────────────┐     是     ┌───────────┐     │
│   │ trx_id >= max_trx_id?        ├────────────►│ 不可见   │     │
│   └───────────┬───────────────────┘            └───────────┘     │
│               │ 否                                               │
│               ▼                                                 │
│   ┌───────────────────────────────┐     是     ┌───────────┐     │
│   │ trx_id 在 m_ids 中?           ├────────────►│ 不可见   │     │
│   └───────────┬───────────────────┘            └───────────┘     │
│               │ 否                                               │
│               ▼                                                 │
│               ┌───────────┐                                     │
│               │ 可见      │                                     │
│               └───────────┘                                     │
└─────────────────────────────────────────────────────────────────┘

4. 不同隔离级别下的 MVCC 行为

MVCC 主要在 READ COMMITTED 和 REPEATABLE READ 隔离级别下工作:

┌─────────────────────────────────────────────────────────────────┐
│               不同隔离级别的 ReadView 创建时机                    │
│                                                                 │
│  ┌────────────────────┐        ┌─────────────────────────────┐  │
│  │    READ COMMITTED  │        │      REPEATABLE READ       │  │
│  ├────────────────────┤        ├─────────────────────────────┤  │
│  │                    │        │                             │  │
│  │ 每次SELECT时创建新的 │        │ 事务开始时创建一次ReadView   │  │
│  │ ReadView           │        │ 之后所有查询复用这个ReadView │  │
│  │                    │        │                             │  │
│  └────────────────────┘        └─────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4.1 READ COMMITTED

  • 每次SELECT都会创建一个新的ReadView
  • 可以看到其他已提交事务的更改
  • 解决了脏读问题,但可能出现不可重复读

4.2 REPEATABLE READ(MySQL默认)

  • 在事务开始时创建一个ReadView,之后的查询都复用这个ReadView
  • 在整个事务过程中,对已经读取的数据,其他事务的更新对当前事务不可见
  • 解决了不可重复读问题

5. MVCC的实际例子

假设我们有一个简单的表格和以下操作序列:

创建表: CREATE TABLE user(id INT PRIMARY KEY, name VARCHAR(20));
初始数据: INSERT INTO user VALUES(1, '小明');

接下来,有三个事务同时操作这条记录:

┌─────────────────────────────────────────────────────────────────┐
│                       事务并发执行示例                            │
│                                                                 │
│  时间 │ 事务A(trx_id=10)  │ 事务B(trx_id=20)  │ 事务C(trx_id=30)  │
│ ─────┼──────────────────┼──────────────────┼────────────────── │
│  t1  │ BEGIN;           │                  │                   │
│  t2  │                  │ BEGIN;           │                   │
│  t3  │                  │                  │ BEGIN;            │
│  t4  │ SELECT * FROM    │                  │                   │
│      │ user WHERE id=1; │                  │                   │
│      │ 结果: '小明'      │                  │                   │
│  t5  │                  │ UPDATE user SET  │                   │
│      │                  │ name='小红'       │                   │
│      │                  │ WHERE id=1;      │                   │
│  t6  │                  │ COMMIT;          │                   │
│  t7  │ SELECT * FROM    │                  │                   │
│      │ user WHERE id=1; │                  │                   │
│      │ 结果(RC): '小红'  │                  │                   │
│      │ 结果(RR): '小明'  │                  │                   │
│  t8  │                  │                  │ UPDATE user SET   │
│      │                  │                  │ name='小黑'        │
│      │                  │                  │ WHERE id=1;       │
│  t9  │                  │                  │ COMMIT;           │
│ t10  │ SELECT * FROM    │                  │                   │
│      │ user WHERE id=1; │                  │                   │
│      │ 结果(RC): '小黑'  │                  │                   │
│      │ 结果(RR): '小明'  │                  │                   │
│ t11  │ COMMIT;          │                  │                   │
└─────────────────────────────────────────────────────────────────┘

版本链变化过程:

初始状态:
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=1 │ROLL_PTR │ROW_ID   │name="小明" │
└─────────┴─────────┴─────────┴───────────┘

事务B更新后:
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=20│ROLL_PTR │ROW_ID   │name="小红" │
└─────────┴─────────┴─────────┴───────────┘
    │
    ▼
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=1 │ROLL_PTR │ROW_ID   │name="小明" │
└─────────┴─────────┴─────────┴───────────┘

事务C更新后:
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=30│ROLL_PTR │ROW_ID   │name="小黑" │
└─────────┴─────────┴─────────┴───────────┘
    │
    ▼
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=20│ROLL_PTR │ROW_ID   │name="小红" │
└─────────┴─────────┴─────────┴───────────┘
    │
    ▼
┌─────────┬─────────┬─────────┬───────────┐
│TRX_ID=1 │ROLL_PTR │ROW_ID   │name="小明" │
└─────────┴─────────┴─────────┴───────────┘

6. MVCC的优缺点

优点:

  • 提高并发性能,读不阻塞写,写不阻塞读
  • 解决了读-写冲突问题
  • 支持事务的隔离级别实现

缺点:

  • 需要额外的存储空间维护旧版本数据
  • 需要定期清理过时的旧版本数据
  • 实现较为复杂

总结

MVCC 是 MySQL InnoDB 存储引擎中实现高并发的关键技术,通过在每行记录后面保存两个隐藏的列(事务ID和回滚指针)来实现的。它能够让不同事务的读、写操作并发执行,同时保证事务的隔离性。根据不同的隔离级别,MySQL 会采用不同的策略来创建和维护 ReadView,从而影响数据的可见性。


网站公告

今日签到

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