Carry の Blog Carry の Blog
首页
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • Python
  • Shell
  • MySQL8-SOP手册
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Carry の Blog

好记性不如烂键盘
首页
  • Nginx
  • Prometheus
  • Iptables
  • Systemd
  • Firewalld
  • Docker
  • Sshd
  • DBA工作笔记
  • MySQL
  • Redis
  • TiDB
  • Elasticsearch
  • Python
  • Shell
  • MySQL8-SOP手册
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL8-SOP

  • MySQL实战45讲学习笔记

    • MySQL45讲学习笔记
    • MySQL基础架构
    • MySQL日志系统 (Redo Log 与 Binlog)
    • MySQL字符串字段索引优化
    • MySQL索引原理与优化
    • MySQL锁机制详解
    • MySQL事务与MVCC机制
      • 1. 事务的 ACID 特性
      • 2. 并发事务带来的问题
      • 3. SQL 标准的事务隔离级别
      • 4. MySQL 事务启动方式
      • 5. 多版本并发控制 (MVCC)
        • MVCC 下的隔离级别实现
    • MySQL普通索引与唯一索引的选择
    • MySQL优化器如何选择索引
    • MySQL抖动刷脏页
    • 表空间管理与回收
    • count函数详解
    • 日志索引
    • orderby工作原理
    • 随机排序实现
    • SQL性能差异函数与转换
    • 慢查询分析锁与版本
    • 幻读与间隙锁
    • 加锁规则分析
    • 应急性能优化方法
    • 数据持久化保证
    • 主备一致性原理
    • 高可用架构与切换
    • 备库延迟分析与优化
    • 主备切换GTID
    • 读写分离实践与问题
    • 数据库健康检查
    • 锁与死锁
    • 数据误删恢复
    • Kill命令详解
    • 查询与内存使用分析
    • Join原理与选择
    • 临时表原理与应用
    • 内部临时表详解
    • Memory引擎详解
    • 自增ID详解
    • Insert加锁分析
    • 表复制方法比较
    • Grant与权限管理
    • 分区表详解
    • SQL语句中的Join问题
    • 自增ID用尽问题
  • 专题系列
  • MySQL实战45讲学习笔记
Carry の Blog
2024-07-27
目录

MySQL事务与MVCC机制

# MySQL 事务与 MVCC 机制

事务 (Transaction) 是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。它主要用于保证数据的一致性。

# 1. 事务的 ACID 特性

事务具有四个标准属性,通常称为 ACID 特性:

  • 原子性 (Atomicity): 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。如果事务失败,所有已执行的操作都必须回滚到事务开始前的状态。InnoDB 通过 undo log 来实现原子性。
  • 一致性 (Consistency): 事务必须使数据库从一个一致性状态变换到另一个一致性状态。事务执行的结果必须是使数据满足所有的约束(如主键约束、外键约束、业务规则等)。一致性是事务追求的最终目标,原子性、隔离性、持久性都是为了保证一致性。
  • 隔离性 (Isolation): 事务的执行不受其他并发事务的干扰。事务隔离分为不同的级别,用于平衡隔离程度和并发性能。InnoDB 通过锁机制和 MVCC 来实现隔离性。
  • 持久性 (Durability): 一个事务一旦提交,它对数据库中数据的改变就是永久性的,接下来的其他操作或故障不应对其有任何影响。InnoDB 通过 redo log 来保证持久性。

注意

事务支持是在存储引擎层实现的。并非所有 MySQL 存储引擎都支持事务,例如 MyISAM 就不支持。InnoDB 是支持事务的常用引擎。

# 2. 并发事务带来的问题

在多个事务同时执行时,如果没有有效的隔离机制,可能会出现以下问题:

  • 脏读 (Dirty Read): 一个事务读取到了另一个未提交事务修改的数据。如果那个事务最终回滚,那么读取到的数据就是无效的“脏”数据。
  • 不可重复读 (Non-Repeatable Read): 在同一个事务内,多次读取同一行数据,结果却不一样。通常是因为在事务执行期间,有其他事务提交了对该行数据的修改。侧重于修改。
  • 幻读 (Phantom Read): 在同一个事务内,多次执行相同的范围查询,结果集中的行数不一样。通常是因为在事务执行期间,有其他事务插入或删除了满足查询条件的新行。侧重于新增或删除。

# 3. SQL 标准的事务隔离级别

SQL 标准定义了四种隔离级别,隔离级别越高,数据一致性越好,但并发性能越差:

  1. 读未提交 (Read Uncommitted):
    • 最低级别,允许读取未提交的数据变更(脏读)。
    • 基本不使用。
  2. 读提交 (Read Committed):
    • 一个事务只能读取到其他事务已经提交的数据变更。
    • 解决了脏读问题。
    • 每次 SELECT 都会创建新的快照 (Read View),可能导致不可重复读和幻读。
    • Oracle、SQL Server 等数据库的默认隔离级别。
  3. 可重复读 (Repeatable Read):
    • 保证在同一个事务中多次读取相同记录的结果是一致的。
    • 解决了脏读和不可重复读问题。
    • 事务启动时创建快照 (Read View),整个事务期间都使用这个快照。
    • MySQL (InnoDB) 的默认隔离级别。
    • 理论上仍可能出现幻读,但 InnoDB 通过间隙锁 (Gap Lock) 机制在一定程度上解决了幻读问题(特别是在当前读场景下)。
  4. 串行化 (Serializable):
    • 最高级别,强制事务串行执行。
    • 通过对所有读取的行都加读锁,对写入的行加写锁来实现。
    • 解决了脏读、不可重复读和幻读问题。
    • 并发性能极差,一般不使用。
隔离级别 脏读 不可重复读 幻读
读未提交 √ √ √
读提交 × √ √
可重复读 (默认) × × △
串行化 × × ×

(√: 可能出现, ×: 不会出现, △: InnoDB 通过间隙锁部分解决)

# 4. MySQL 事务启动方式

  • 显式启动:
    • START TRANSACTION 或 BEGIN。
    • 提交: COMMIT。
    • 回滚: ROLLBACK。
  • 自动提交:
    • MySQL 默认开启 autocommit (SELECT @@autocommit; 查看,值为 1 表示开启)。每条 SQL 语句都被当作一个独立的事务自动提交。
    • 关闭自动提交: SET autocommit = 0;。之后需要手动执行 COMMIT 或 ROLLBACK。执行 SET autocommit = 1; 可恢复默认行为。

# 5. 多版本并发控制 (MVCC)

MVCC (Multi-Version Concurrency Control) 是 InnoDB 引擎用来实现在读提交 (RC) 和可重复读 (RR) 这两种隔离级别下,处理读写冲突时避免加锁,从而提高并发性能的一种机制。它使得在同一时刻,不同事务看到的可能是同一份数据的不同版本。

核心组件:

  • 隐藏字段: InnoDB 会为每行数据添加几个隐藏字段:
    • DB_TRX_ID: 创建或最后修改该行数据的事务 ID。
    • DB_ROLL_PTR: 回滚指针,指向该行上一个版本的 undo log 记录。
    • DB_ROW_ID: 如果表没有主键且没有唯一非空索引,InnoDB 会自动生成一个隐藏的行 ID。
  • Undo Log:
    • 记录数据被修改前的值。用于事务回滚和 MVCC。
    • 当事务修改数据时,会将旧版本数据存入 undo log,并通过 DB_ROLL_PTR 指针形成一个版本链。
  • Read View (一致性视图):
    • 事务进行快照读(普通的 SELECT 语句)时,会基于当前数据库状态生成一个 Read View。它决定了事务能看到哪些版本的数据。
    • Read View 主要包含:
      • m_ids: 创建 Read View 时,当前活跃(未提交)的事务 ID 列表。
      • min_trx_id: m_ids 中的最小事务 ID。
      • max_trx_id: 创建 Read View 时,系统应该分配给下一个事务的 ID (即当前最大事务 ID + 1)。
      • creator_trx_id: 创建该 Read View 的事务 ID。
  • 可见性判断规则: 当事务访问某行数据时,会根据该行记录的 DB_TRX_ID 和 Read View 进行比较,判断该版本是否可见:
    1. 如果 DB_TRX_ID < min_trx_id:表明该版本是在当前所有活跃事务之前就已经提交的,可见。
    2. 如果 DB_TRX_ID >= max_trx_id:表明该版本是在 Read View 创建之后才开启的事务生成的,不可见。需要沿着 undo log 链找到上一个版本,再进行判断。
    3. 如果 min_trx_id <= DB_TRX_ID < max_trx_id:
      • 若 DB_TRX_ID 在 m_ids 列表中:表明该版本是由 Read View 创建时还活跃的事务生成的,不可见。需要沿着 undo log 链找上一个版本。
      • 若 DB_TRX_ID 不在 m_ids 列表中:表明该版本是由 Read View 创建时已经提交的事务生成的,可见。
    4. 如果 DB_TRX_ID 等于 creator_trx_id:表明是当前事务自己修改的,可见。

# MVCC 下的隔离级别实现

  • 读提交 (RC): 每次 SELECT 语句执行时都会重新生成一个新的 Read View。这导致在同一个事务中,后续的 SELECT 可能会看到其他事务在此期间提交的修改,从而产生不可重复读。
  • 可重复读 (RR): 事务启动后的第一个 SELECT 语句执行时(或者 START TRANSACTION WITH CONSISTENT SNAPSHOT 时)创建 Read View,并且整个事务期间都复用这个 Read View。这保证了事务期间读取到的数据版本总是一致的,避免了不可重复读。

快照读 vs 当前读

  • 快照读 (Snapshot Read): 普通的 SELECT 语句(不加锁),读取的是 Read View 决定的数据版本,不加锁。
  • 当前读 (Current Read): 读取数据库中最新的、已提交的版本,并对读取的记录加锁,保证其他并发事务不能修改这些记录。常见的当前读语句:
    • SELECT ... LOCK IN SHARE MODE (共享锁, S锁)
    • SELECT ... FOR UPDATE (排他锁, X锁)
    • INSERT, UPDATE, DELETE (排他锁, X锁) 在 RR 级别下,当前读需要使用记录锁 (Record Lock)、间隙锁 (Gap Lock) 或 Next-Key Lock (记录锁 + 间隙锁) 来防止幻读。

#MySQL#事务#ACID#隔离级别#MVCC#Read View#undo log#学习笔记
上次更新: 6/21/2025

← MySQL锁机制详解 MySQL普通索引与唯一索引的选择→

最近更新
01
表空间管理与回收
06-21
02
MySQL抖动刷脏页
06-21
03
count函数详解
06-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式