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 隔离级别
相关推荐
liyuhh9853 小时前
《接口幂等性设计的三种方案与实践》
面试
Ruihong4 小时前
Vue v-html 与 v-text 转 React:VuReact 怎么处理?
vue.js·react.js·面试
小徐不徐说5 小时前
面试C++易错点总结
开发语言·c++·面试·职场和发展·程序设计·工作
一只幸运猫.5 小时前
字节跳动Java大厂面试版
java·开发语言·面试
看我干嘛!6 小时前
mysql主从配置一主一从
数据库·mysql
一只大袋鼠6 小时前
Java JDBC 封装:从原生写法到工具类封装 + 增删改查
java·开发语言·数据库·mysql
YummyJacky6 小时前
阿里ai应用开发面试
面试·职场和发展
椰猫子6 小时前
数据库(数据库相关概念、MySQL数据库、SQL(DDL、DML、DQL))
数据库·sql·mysql
j_xxx404_6 小时前
数据库基础夯实:从零手写DDL与DML,MySQL核心语法实战解析
数据库·mysql