Mysql-MVCC

MVCC (Multi-Version Concurrency Control) 多版本并发控制

是什么

MVCC 是一种数据库并发控制机制,让读操作不加锁,通过保存数据的多个版本,实现读写互不阻塞。

核心目标:读不阻塞写,写不阻塞读,解决读写冲突问题。

怎么用

MVCC 是数据库引擎内部机制,不需要开发者手动使用。在 MySQL InnoDB 中:

  • 普通 SELECT 自动走 MVCC 快照读(不加锁)
  • 加锁读SELECT ... FOR UPDATE / LOCK IN SHARE MODE)走当前读(不走 MVCC)
sql 复制代码
-- 快照读,走MVCC,不加锁
SELECT * FROM account WHERE id = 1;

-- 当前读,不走MVCC,加锁
SELECT * FROM account WHERE id = 1 FOR UPDATE;

-- 写操作也是当前读
UPDATE account SET balance = balance - 100 WHERE id = 1;

实现原理(InnoDB)

InnoDB 的 MVCC 依赖三个核心组件:

1. 隐藏字段

每行数据有两个隐藏列:

隐藏字段 说明
DB_TRX_ID 最后修改该行的事务 ID
DB_ROLL_PTR 回滚指针,指向 undo log 中的上一个版本

2. Undo Log(版本链)

每次修改数据时,旧版本写入 undo log,通过 DB_ROLL_PTR 串成版本链

ini 复制代码
当前行 (trx_id=5, roll_ptr→)
  → 旧版本v2 (trx_id=3, roll_ptr→)
    → 旧版本v1 (trx_id=1, roll_ptr=NULL)

3. Read View(读视图)

事务执行快照读时生成,决定当前事务能看到哪个版本。包含四个关键字段:

字段 说明
m_ids 生成 Read View 时,所有活跃(未提交)事务 ID 列表
min_trx_id 活跃事务中最小 ID
max_trx_id 下一个将分配的事务 ID(即当前最大事务 ID + 1)
creator_trx_id 创建该 Read View 的事务 ID

可见性判断规则

对版本链上某个版本,按以下顺序判断:

markdown 复制代码
1. trx_id == creator_trx_id → 可见(自己修改的)
2. trx_id < min_trx_id → 可见(事务已提交)
3. trx_id >= max_trx_id → 不可见(事务在 Read View 之后才开始)
4. min_trx_id <= trx_id < max_trx_id:
   - trx_id 在 m_ids 中 → 不可见(事务未提交)
   - trx_id 不在 m_ids 中 → 可见(事务已提交)
5. 当前版本不可见 → 沿 roll_ptr 找上一个版本,重复判断

具体例子

场景:账户转账

sql 复制代码
CREATE TABLE account (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    balance INT
) ENGINE=InnoDB;

INSERT INTO account VALUES (1, 'Alice', 1000);  -- trx_id=1, 已提交

此时数据行:id=1, name='Alice', balance=1000, DB_TRX_ID=1, DB_ROLL_PTR=NULL

并发操作

ini 复制代码
时间线:
T1: 事务A (trx_id=2) 开始,未提交
T2: 事务B (trx_id=3) 开始,未提交
T3: 事务A UPDATE account SET balance=800 WHERE id=1;
    → 旧版本写入undo log,当前行 DB_TRX_ID=2, DB_ROLL_PTR→旧版本(balance=1000, trx_id=1)
T4: 事务B SELECT * FROM account WHERE id=1;  -- 快照读
T5: 事务C (trx_id=4) UPDATE account SET balance=600 WHERE id=1;  -- 等待事务A提交

T4 时刻事务B的 Read View:

  • m_ids = [2, 3](事务A、B都未提交)
  • min_trx_id = 2
  • max_trx_id = 5
  • creator_trx_id = 3

判断可见性:

  1. 当前行 trx_id=22 < min_trx_id(2)?否
  2. 2 >= max_trx_id(5)?否
  3. 2m_ids=[2,3] 中? → 不可见(事务A未提交)
  4. 沿 roll_ptr 找到旧版本 trx_id=1
  5. 1 < min_trx_id(2)可见

结果:事务B 读到 balance=1000(事务A修改前的值)

RC vs RR 的区别

隔离级别 Read View 生成时机 效果
RC(读已提交) 每次 SELECT 都生成新 Read View 能读到其他事务已提交的最新值
RR(可重复读) 事务内第一次 SELECT 生成,后续复用 事务内多次读结果一致
sql 复制代码
-- RR级别下
-- 事务B第一次SELECT → 生成Read View → 读到balance=1000
-- 事务A提交
-- 事务B第二次SELECT → 复用Read View → 仍然读到balance=1000(可重复读)

-- RC级别下
-- 事务B第一次SELECT → 生成Read View → 读到balance=1000
-- 事务A提交
-- 事务B第二次SELECT → 重新生成Read View → 读到balance=800(读已提交)

总结

维度 说明
本质 用版本链 + 读视图实现无锁读
核心组件 隐藏字段 + Undo Log + Read View
优势 读写不互斥,高并发性能好
局限 Undo Log 占空间;长事务导致旧版本无法回收
适用 MySQL InnoDB 的 RC/RR 隔离级别
相关推荐
孓最求完美13 小时前
Charles 进阶抓包教程:突破 SSL Pinning、代理检测与 Flutter 抓包限制
面试
thesky12345613 小时前
智能体面试准备(九):工作流编排——DAG 与状态机两种范式的取舍与手写实现
面试·agent·状态机·工作流·智能体
Revolution6114 小时前
reactive 对象重新赋值后,表单为什么没有恢复默认值
前端·vue.js·面试
HeiSenBerg14 小时前
Android Handler 机制完全解析:从源码到内存泄漏,一篇就够了
面试
瞬间&永恒~14 小时前
【MySQL】 主从复制多拓扑搭建实验
运维·数据库·mysql·云原生
井川廊咏15 小时前
vi 删除指定范围的行,不用再反复按 dd
数据库·mysql
小码农 - 初15 小时前
MySQL前言
mysql
only-qi15 小时前
大模型Agent面试攻略:落地工程痛点、评估体系与Agentic RAG核心精讲
人工智能·算法·面试·职场和发展·langchain·rag
z落落15 小时前
StudentInfo表 分页、增删改 存储过程+C# WinForm 存储过程实现分页和增删改查
数据库·sql·mysql
数据库小学妹16 小时前
MySQL联合索引失效怎么解决?从key_len逆向分析
数据库·mysql·性能优化·索引失效·联合索引·最左前缀·keylen