MySQL数据库笔记——多版本并发控制MVCC

大家好,这里是Good Note,关注 公主号:Goodnote,本文详细介绍MySQL的并发控制:多版本并发控制MVCC。

文章目录

背景介绍

许多人认为 MVCC(Multi-Version Concurrency Control,多版本并发控制) 是一种乐观锁的实现方式,我们先来了解一下什么是乐观锁和悲观锁。

数据库并发控制------锁机制

在数据库系统中,并发控制 是保证多个事务在并发执行时数据一致性的核心技术。传统的并发控制方法是使用 ,它是一种直接而有效的解决方案。

  • 锁的分类
    • DQL (Data Query Language,数据查询语言):查询数据(如 SELECT)时使用 读锁
    • DML (Data Manipulation Language,数据操作语言):对数据进行增、删、改操作(如 INSERTDELETEUPDATE)时使用 写锁
    • DDL (Data Definition Language,数据库定义语言):定义和修改表结构(如 CREATE TABLEDROP TABLE)时,通常会使用 元数据锁
悲观锁和乐观锁
悲观锁
  • 概念
    悲观锁假定会发生并发冲突,因此在对资源进行操作之前,会先加锁,确保其他事务无法同时访问该资源。
  • 特点
    • 需要加锁,锁定资源后,其他线程对该资源的操作会被阻塞。
    • 开销较大,可能导致性能下降。
  • 应用场景
    • 适用于并发冲突频繁的场景。
  • 例子
    数据库中的 行级锁 或 Java 中的 synchronized 关键字。
乐观锁
  • 概念
    乐观锁假定不会发生并发冲突,因此不加锁,而是在更新数据时,通过比较版本号或条件检查来保证操作的正确性。
  • 特点
    • 不加锁,操作更轻量级。
    • 需要在操作完成后检查是否发生冲突。
  • 应用场景
    • 适用于并发冲突较少的场景。
  • 实现方式
    • 版本号机制:每次修改数据时,更新版本号。更新操作成功的前提是版本号没有变化。
    • CAS(Compare And Swap):通过原子性比较和更新操作实现乐观锁。

数据库并发控制------MVCC 的引入

许多人认为 MVCC(Multi-Version Concurrency Control,多版本并发控制) 是一种乐观锁的实现方式,但 MVCC 的核心在于通过 版本控制和可见性算法 来实现数据库的并发控制。InnoDB 的 MVCC 通过隐藏字段、Undo Log 和 Read View 协同工作,实现了高效的多版本并发控制:

  1. 隐藏字段

    • 提供版本控制信息,判断事务的可见性。
  2. Undo Log

    • 保存数据的历史版本,支持事务回滚和快照读。
  3. Read View

    • 在事务启动时生成的元数据,用于确定哪些数据版本对事务可见。

MVCC 和锁机制的对比

特性 锁(悲观锁/乐观锁) MVCC
加锁开销 悲观锁需要加锁,开销较大;乐观锁无需加锁 不加锁,依赖多版本数据
并发性能 读写互斥,可能导致线程阻塞 读写分离,读操作不阻塞写
实现方式 直接加锁或通过版本号/CAS 判断 通过 Undo Log 和事务视图维护多版本
适用场景 并发冲突频繁的场景(悲观锁) 读多写少的场景,且冲突较少
优缺点 加锁开销大,读写冲突会阻塞 空间开销较大,但读性能更优

MySQL 的多版本并发控制 (MVCC)

多版本并发控制 (MVCC, Multi-Version Concurrency Control) 是 MySQL 用于实现事务隔离的一种机制,主要应用于 InnoDB 存储引擎 。通过 MVCC,MySQL 可以在高并发环境下实现 读写并行,同时减少锁的使用,提高性能。

快照读和当前读

  • 快照读(Snapshot Read)

    • 读取的是数据的快照版本(历史版本),即事务开始时的数据状态,而不是最新的
    • 常见的 SELECT 语句都是快照读,除非显式使用加锁查询。
  • 当前读(Current Read)

    • 读取的是最新版本的数据,并且会对读取的数据加锁以确保一致性
    • 常见的当前读操作包括:
      • SELECT ... FOR UPDATE
      • SELECT ... LOCK IN SHARE MODE
      • UPDATE
      • DELETE
      • INSERT

快照读和当前读的对比

操作类型 快照读(Snapshot Read) 当前读(Current Read)
使用场景 普通 SELECT 查询 加锁查询(SELECT ... FOR UPDATE 等)
是否加锁 不加锁 加锁
数据版本 读取快照版本,使用 Undo Log 读取当前版本,可能会阻塞
是否支持 MVCC 支持 支持,但需要额外的锁操作

隐藏的系统列

InnoDB 存储引擎会为每一行记录添加了以下 三个隐藏的系统列,用于实现 MVCC:

isDelete DB_TRX_ID DB_ROLL_PTR RowID(可选) id name password
是否删除 事务 ID 回滚指针 隐藏的自增 ID。如果表未指定主键,系统会自建 id name password
字段名 含义
isDelete 标记该记录是否被逻辑删除,删除标志不是单独的字段,而是存储在记录头信息中,用户不可见。
DB_TRX_ID 记录每行数据最近一次修改(插入/更新)所对应的事务 ID(Read View中的事务ID)。
DB_ROLL_PTR 回滚指针,指向该记录的 undo log 日志,保存行的历史版本。

Undo Log(回滚日志)

Undo Log 是 InnoDB 存储引擎用于实现事务回滚、快照读(MVCC)的重要机制之一。它记录数据修改前的旧版本,并通过回滚指针(DB_ROLL_PTR)形成一条 Undo 日志链。

  • 功能

    1. 事务回滚
      • 当事务未提交或被回滚时,Undo Log 提供修改前的数据,用于恢复到原始状态,撤销未提交事务对数据库的影响。
    2. 多版本控制(MVCC)
      • Undo Log 保存了数据的历史版本,通过回滚指针(DB_ROLL_PTR),其他事务可以通过 Undo Log 获取旧版本数据。
  • Undo Log 的特性

    • 逻辑日志
      • 记录逻辑上的操作。例如:
        • 删除一条记录时,Undo Log 会记录一个对应的"插入操作"。
        • 更新一条记录时,Undo Log 会记录一个对应的"反向更新操作"。
    • 存储位置
      • Undo Log 存储在 回滚段 中。
  • Undo Log 的分类

    1. Insert Undo Log
      • 记录事务插入数据时的日志。
      • 特点:事务提交后即可丢弃,因为没有其他事务需要访问它。
    2. Update Undo Log
      • 记录事务更新或删除数据时的日志。
      • 特点 :事务提交后,仍需保留以支持快照读,只有当没有比该日志更早的 Read View 存在时,才能删除。
  • 问题与优化

    • 长事务可能导致 Undo Log 无法及时清理,因为较早的 Read View 仍然需要访问旧版本数据。这会导致存储空间占用过大,建议避免长时间未提交的事务。

Read View(读视图)

Read View 是事务在执行快照读(Snapshot Read)时生成的一种快照机制,用于判断当前事务对哪些数据版本可见。

  • 功能

    • Read View 确保事务在快照读时能够看到一致性的数据。
    • 通过可见性算法(Visibility Algorithm)判断某个数据版本是否对当前事务可见。
  • Read View 的组成

    1. alive_trx_list
      • 当前系统中活跃的事务 ID 列表,包含所有未提交事务的 ID。
    2. up_limit_id
      • alive_trx_list 中的最小事务 ID。
    3. low_limit_id
      • 系统当前分配的最大事务 ID 加 1。

可见性算法(Visibility Algorithm)

在生成 Read View 后,InnoDB 通过以下步骤判断数据版本(DB_TRX_ID)是否对当前事务可见, MVCC 的可重复读(Repeatable Read)隔离级别判断如下:

  1. 判断是否早于活跃事务的最小 ID

    • 如果 DB_TRX_ID < up_limit_id,表明该版本在生成 Read View 前已提交,对当前事务可见。
  2. 判断是否晚于最新事务的最大 ID

    • 如果 DB_TRX_ID >= low_limit_id,表明该版本在生成 Read View 后才生成,对当前事务不可见。
  3. 判断是否属于活跃事务

    • 如果 DB_TRX_IDalive_trx_list 中,说明生成 Read View 时该事务仍未提交,因此该版本对当前事务不可见。
  4. 通过回滚指针查找可见版本

    • 如果数据版本不可见且 ROLL_PTR 不为空,则通过 ROLL_PTR 指向的 Undo Log 查找更早的版本,重复上述判断,直到找到可见的版本。

MVCC 支持的事务隔离级别

1. 读未提交(Read Uncommitted)

  • Read View

    • 不使用 Read View。
    • 读取数据时直接读取最新版本,无论数据是否由其他事务提交。
  • 可见性规则

    • 所有事务的最新修改版本对当前事务可见
    • 即使其他事务未提交的数据,也可以被读取(会发生脏读)。
  • 特点

    • 无需 Undo Log,也不使用 alive_trx_list 等 Read View 属性。

2. 读已提交(Read Committed)

  • Read View

    • 每次查询都会生成新的 Read View,因此每次查询的结果可能不同
    • Read View 只在当前查询的上下文中生效,不跨查询复用。
  • 可见性规则

    1. 如果 DB_TRX_ID 小于 up_limit_id(即数据版本在当前查询的 Read View 生成之前已提交),则该版本对当前事务可见
    2. 如果 DB_TRX_ID 在活跃事务列表中,说明该版本由未提交事务生成,对当前事务不可见
  • 特点

    • 数据版本的可见性随着每次查询变化。
    • 防止脏读,但可能发生不可重复读。

3. 可重复读(Repeatable Read)

  • Read View

    • 事务开始时生成一次 Read View,整个事务期间使用同一个快照,确保读取结果一致
    • 该 Read View 的属性(up_limit_idlow_limit_idalive_trx_list)在事务期间不会变化。
  • 可见性规则

    1. 如果 DB_TRX_ID < up_limit_id,说明数据版本在事务开始前已提交,对当前事务可见
    2. 如果 DB_TRX_ID >= low_limit_id,说明数据版本在事务开始后生成,对当前事务不可见
    3. 如果 DB_TRX_IDalive_trx_list 中,说明数据版本由未提交的事务生成,对当前事务不可见
  • 特点

    • 读操作始终基于事务开始时生成的 Read View。
    • 防止脏读和不可重复读,但可能发生幻读。

4. 串行化(Serializable)

  • Read View

    • 不使用 Read View。
    • 通过加锁(如共享锁、排他锁)实现事务隔离。
  • 可见性规则

    • 每次读取时都会加锁,确保当前读操作的可见性。
    • 因为加锁阻塞了其他事务的修改或读取,因此不存在不可见的问题。
  • 特点

    • 防止脏读、不可重复读和幻读。
    • 并发性能较低,但数据一致性最高。

整体工作流程

  1. 事务修改数据时

    • 写入 Undo Log,保存旧版本数据。
    • 更新 DB_TRX_IDDB_ROLL_PTR
  2. 事务执行快照读时

    • 生成 Read View,记录当前系统中活跃事务列表。
    • 判断数据版本是否可见:
      • 若不可见,使用 DB_ROLL_PTR 查找历史版本。
  3. 事务提交时

    • Insert Undo Log 可直接删除。
    • Update Undo Log 保留,用于支持其他事务的快照读。
  4. 事务回滚时

    • 通过 Undo Log 恢复旧版本数据,撤销事务的影响。

总结

  • MVCC 是 InnoDB 存储引擎中实现事务隔离和提高并发性能的关键机制

  • 通过维护多个数据版本和 Undo Log,实现快照读和当前读,避免了大量加锁操作。

  • Undo Log

    • 是 MVCC 的基础,记录旧版本数据,支持事务回滚和快照读。
    • 分类为 Insert Undo Log 和 Update Undo Log。
  • Read View

    • 确保快照读的隔离性,通过可见性算法判断数据版本是否可见。
    • 隔离级别的不同会影响 Read View 的生成时机。
  • 两者配合

    • Undo Log 提供数据的历史版本,Read View 判断哪些版本对当前事务可见,共同实现事务的并发控制和一致性。

MVCC 的优点

  1. 提高并发性能

    • 快照读不需要加锁,避免了读写之间的冲突。
  2. 减少锁开销

    • 大量读操作可以通过读取历史版本完成,无需加锁,提高效率。
  3. 支持事务隔离

    • MVCC 能够在不同的隔离级别下提供一致的数据读取。

MVCC 的局限性

  1. 占用存储空间

    • Undo Log 的存在会增加存储开销,特别是长事务会导致 Undo Log 增长。
  2. 长事务的性能问题

    • 长事务可能会导致 Undo Log 不能被及时清理,增加性能开销。
  3. 仅适用于读写混合场景

    • 如果事务中大量是写操作,MVCC 的优势会减弱,因为写操作仍需加锁。

示例:MVCC 的快照读

1. 表结构

sql 复制代码
CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    status VARCHAR(20)
);

2. 插入数据

sql 复制代码
INSERT INTO orders (status) VALUES ('pending'), ('shipped'), ('delivered');

3. 启动事务并模拟并发查询

事务 A:

sql 复制代码
START TRANSACTION;
SELECT * FROM orders; -- 快照读,读取事务开始时的版本
UPDATE orders SET status = 'cancelled' WHERE id = 1; -- 当前读,修改最新版本

事务 B:

sql 复制代码
START TRANSACTION;
SELECT * FROM orders WHERE id = 1; -- 读取事务 A 修改前的快照版本

结果:

  • 事务 A 在事务 B 提交之前,可以看到修改后的状态。
  • 事务 B 在事务 A 提交之前,读取的是修改前的状态。

历史文章

  1. MySQL数据库笔记------数据库三范式
  2. MySQL数据库笔记------存储引擎(InnoDB、MyISAM、MEMORY、ARCHIVE)
  3. MySQL数据库笔记------常见的几种锁分类
  4. MySQL数据库笔记------索引介绍
  5. MySQL数据库笔记------事务介绍
  6. MySQL数据库笔记------索引结构之B+树
  7. MySQL数据库笔记------索引潜规则(回表查询、索引覆盖、索引下推)
  8. MySQL数据库笔记------索引潜规则(最左前缀原则)
  9. MySQL数据库笔记------常见慢查询优化方式
  10. MySQL数据库笔记------日志介绍
相关推荐
开心工作室_kaic38 分钟前
springboot548二手物品交易boot代码(论文+源码)_kaic
前端·数据库·vue.js·后端·html5
m0_678693332 小时前
深度学习笔记10-数据增强(Tensorflow)
笔记·深度学习·tensorflow
尘浮生4 小时前
Java项目实战II基于微信小程序的家庭大厨(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven
红色的山茶花5 小时前
YOLOv10-1.1部分代码阅读笔记-autobackend.py
笔记·深度学习·yolo
m0_634601665 小时前
2025.1.2
java·服务器·数据库
张乔247 小时前
spring boot项目启动时自定义logo
java·数据库·spring boot
xiaobai12 37 小时前
MySQL图形化界面工具--DataGrip
数据库·mysql
快乐飒男8 小时前
C语言基础18(GDB调试)
c语言·笔记·学习
freejackman8 小时前
Redis快速入门
数据库·redis·缓存
lisacumt8 小时前
【flink-cdc】flink-cdc 3版本debug启动pipeline任务,mysql-doris
大数据·mysql·flink