当前位置: 首页 > news >正文

MVCC理解

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

http://www.xdnf.cn/news/862399.html

相关文章:

  • vscode中无法使用npm node
  • Windows 12确认没了,Win11 重心偏移修Bug
  • 2025年- H65-Lc173--347.前k个高频元素(小根堆,堆顶元素是当前堆元素里面最小的)--Java版
  • HDU-2973 YAPTCHA
  • Maven 构建缓存与离线模式
  • 第二章 进程管理
  • 让音乐“看得见”:使用 HTML + JavaScript 实现酷炫的音频可视化播放器
  • 【论文阅读笔记】Text-to-SQL Empowered by Large Language Models: A Benchmark Evaluation
  • 【RAG优化】rag整体优化建议
  • AI全栈之路:Ubuntu云服务器部署Spring + Vue + MySQL实践指南
  • MySQL索引(index)
  • Mybatis入门到精通
  • Spark实战能力测评模拟题精析【模拟考】
  • 编程技能:格式化打印04,sprintf
  • Ubuntu 16.04 密码找回
  • 区块链安全攻防战:51% 攻击与 Sybil 攻击的应对策略
  • 目标检测任务的评估指标mAP50和mAP50-95
  • OpenCV计算机视觉实战(10)——形态学操作详解
  • 【从前端到后端导入excel文件实现批量导入-笔记模仿芋道源码的《系统管理-用户管理-导入-批量导入》】
  • 目标检测任务的评估指标P-R曲线
  • NPOI操作EXCEL文件 ——CAD C# 二次开发
  • LlamaIndex:解锁LLM潜力的数据编排利器
  • C++性能优化指南
  • Java Stream 高级实战:并行流、自定义收集器与性能优化
  • ODOO12
  • springboot--实战--大事件--文章分类接口开发详解
  • 微软的新系统Windows12未来有哪些新特性
  • 微软重磅发布Magentic UI,交互式AI Agent助手实测!
  • 使用Virtual Serial Port Driver+com2tcp(tcp2com)进行两台电脑的串口通讯
  • RT Thread平台下 基于N32G45x和N32L40x的drv_pwm驱动实现