进阶-InnoDB引擎-MVCC

一、MySQL进阶

"当两个用户同时查看同一份数据,你希望看到的是'当前状态',还是'事务开始时的状态'?"

------ MVCC(多版本并发控制),让数据库在高并发下依然保持"一致性"与"高效性"

为什么需要MVCC?------一场"读写冲突"的危机

在传统数据库中,读操作会阻塞写操作,写操作会阻塞读操作。例如:

  • 事务A:UPDATE orders SET status='shipped' WHERE order_id=100
  • 事务B:SELECT * FROM orders WHERE order_id=100

如果事务B在事务A未提交时执行,会发生什么?

  • 悲观锁:事务B必须等待事务A提交(锁表),导致性能下降
  • 乐观锁:事务B直接读取,但可能读到未提交数据(脏读)

💡 2010年,某电商网站因锁表问题导致双11期间响应时间从50ms飙升至500ms,损失超百万!

这就是 MVCC诞生的背景在不加锁的情况下,实现高并发读写

1. InnoDB引擎-MVCC

📌 什么是MVCC?

MVCC(Multi-Version Concurrency Control) 是一种并发控制机制,它通过 保存数据的多个版本 ,让读操作无需等待写操作,从而实现 "读不阻塞写,写不阻塞读"

📌 两种读操作:

类型 说明 举例 是否加锁
当前读(Current Read) 读取最新数据(已提交) SELECT ... FOR UPDATE UPDATE DELETE ✅ 加锁(X锁)
快照读(Snapshot Read) 读取事务开始时的快照 SELECT * ❌ 不加锁

💡 关键点快照读是MVCC的核心,它利用历史版本实现非阻塞读。

MVCC的核心组件:三个隐式字段

InnoDB 通过 三个隐藏字段(在表中不可见)实现MVCC

字段 作用 类型 说明
DB_TRX_ID 记录最近修改该行的事务ID 6字节 用于判断行是否对当前事务可见
DB_ROLL_PTR 指向Undo Log的指针 7字节 用于找到历史版本
DB_ROW_ID 隐藏的行ID(自增) 6字节 在索引中作为回表的唯一标

💡 为什么需要这些字段?

  • DB_TRX_ID:判断行是否已提交
  • DB_ROLL_PTR:通过Undo Log找到旧版本
  • DB_ROW_ID:在索引中唯一标识行(即使主键未指定)

Undo Log:数据的"时间胶囊"

Undo Log 是 MVCC 的基础,它记录了 数据修改前的旧值,用于:

  • 事务回滚
  • MVCC 快照读

📌 Undo Log 两种类型:

类型 作用 说明
INSERT 记录插入的行 用于事务回滚(删除时需恢复)
UPDATE 记录修改前的旧值 用于快照读和回滚(更新时需恢复)

💡 Undo Log 的存储

由 InnoDB 的 Undo Tablespace 管理,可以配置多个 Undo Log 文件(MySQL 5.7+ 支持独立Undo表空间)。

数据版本链:Undo Log 的"链式存储"

当一行数据被多次修改时,Undo Log 会形成 版本链

复制代码
最新版本 → (DB_ROLL_PTR) → 旧版本 → (DB_ROLL_PTR) → 更旧版本 → ... → 初始版本

💡 举例

  • 初始:balance=1000
  • 事务1:UPDATE balance=800 → 生成Undo Log(记录1000)
  • 事务2:UPDATE balance=500 → 生成Undo Log(记录800)

版本链:500 → 800 → 1000

ReadView:MVCC的"时间机器"

ReadView 是事务开始时生成的快照,它决定了哪些版本对当前事务可见。

📌 ReadView 包含的关键信息:

字段 说明
min_trx_id 活跃事务最小ID(当前正在执行的事务ID)
max_trx_id 活跃事务最大ID(当前正在执行的事务ID上限)
m_ids 活跃事务ID列表(当前正在执行的事务ID集合)
python 复制代码
def is_visible(trx_id, readview):
    if trx_id < readview.min_trx_id:  # 已提交
        return True
    elif trx_id >= readview.max_trx_id:  # 未提交
        return False
    else:  # 在活跃事务列表中
        if trx_id in readview.m_ids:  # 未提交
            return False
        else:  # 已提交
            return True

💡 关键点

  • RR级别(可重复读):事务开始时生成ReadView,后续查询都使用同一ReadView
  • RC级别(读已提交):每次查询都生成新的ReadView

MVCC工作原理:一次事务的"时间穿越"

假设执行:

sql 复制代码
-- 事务A(ID=100)
START TRANSACTION;
SELECT * FROM accounts WHERE user_id=1;  -- 快照读
UPDATE accounts SET balance=500 WHERE user_id=1;  -- 当前读
COMMIT;

-- 事务B(ID=101)
START TRANSACTION;
SELECT * FROM accounts WHERE user_id=1;  -- 快照读

📌 MVCC如何工作?

  1. 事务A开始
    • 生成ReadView:min_trx_id=100, max_trx_id=102, m_ids=[100]
    • 执行SELECT:快照读,使用ReadView判断可见性
  2. 事务A执行UPDATE
    • 修改行,生成Undo Log(记录旧值:1000 → 500)
    • DB_TRX_ID 更新为100
    • DB_ROLL_PTR 指向Undo Log
  3. 事务B执行SELECT
    • 生成ReadView:min_trx_id=101, max_trx_id=102, m_ids=[101]
    • 读取行:DB_TRX_ID=100 < 101 → 可见(值为1000)

🌟 效果:事务B在事务A未提交时,读到的是1000(事务A开始时的值),而非500(当前值)。

MVCC在不同隔离级别下的表现

隔离级别 快照读 可见性规则 幻读 说明
读未提交 不使用MVCC 直接读最新数据 无MVCC
读已提交 使用MVCC 每次查询生成新ReadView 无法避免不可重复读
可重复读 使用MVCC 事务开始时生成ReadView 通过Next-Key Lock避免幻读
串行化 不使用MVCC 强制加锁 无MVCC

💡 为什么RR级别能避免幻读?

InnoDB 在RR级别下使用 Next-Key Lock(记录锁 + 间隙锁),在读取时不仅锁住记录,还锁住间隙,防止其他事务插入新数据。

相关推荐
m0_466525292 分钟前
绿盟科技风云卫AI安全能力平台成果重磅发布
大数据·数据库·人工智能·安全
爱学习的阿磊39 分钟前
使用Fabric自动化你的部署流程
jvm·数据库·python
枷锁—sha1 小时前
【SRC】SQL注入快速判定与应对策略(一)
网络·数据库·sql·安全·网络安全·系统安全
惜分飞1 小时前
ORA-600 kcratr_nab_less_than_odr和ORA-600 4193故障处理--惜分飞
数据库·oracle
chian-ocean1 小时前
CANN 生态进阶:利用 `profiling-tools` 优化模型性能
数据库·mysql
m0_550024631 小时前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python
AC赳赳老秦1 小时前
代码生成超越 GPT-4:DeepSeek-V4 编程任务实战与 2026 开发者效率提升指南
数据库·数据仓库·人工智能·科技·rabbitmq·memcache·deepseek
啦啦啦_99991 小时前
Redis-2-queryFormat()方法
数据库·redis·缓存
玄同7652 小时前
SQLite + LLM:大模型应用落地的轻量级数据存储方案
jvm·数据库·人工智能·python·语言模型·sqlite·知识图谱
吾日三省吾码2 小时前
别只会“加索引”了!这 3 个 PostgreSQL 反常识优化,能把性能和成本一起打下来
数据库·postgresql