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

    • 第一章 概述
    • 第二章 环境准备
    • 第三章 安装部署规范
    • 第四章 ReplicaSet高可用配置
    • 第五章 监控与日常维护
    • 第六章 故障处理手册
    • 第七章 安全与权限管理
    • 第八章 扩展与升级方案
    • 附录
  • MySQL实战45讲学习笔记

  • 专题系列
  • MySQL实战45讲学习笔记
Carry の Blog
2024-07-28
目录

MySQL事务隔离级别的实现原理

# MySQL 事务隔离级别的实现原理

MySQL 的事务隔离是通过 锁机制 和 MVCC(多版本并发控制) 机制共同实现的。本文将深入探讨 MySQL 中事务隔离级别的实现原理,以及各种隔离级别下可能出现的问题和解决方案。

# 1. 事务隔离级别回顾

MySQL 支持的四种隔离级别:

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED(读未提交) 可能 可能 可能
READ COMMITTED(读已提交) 不可能 可能 可能
REPEATABLE READ(可重复读) 不可能 不可能 理论上可能,InnoDB实际上通常不会
SERIALIZABLE(串行化) 不可能 不可能 不可能

MySQL InnoDB 默认使用 REPEATABLE READ(可重复读) 隔离级别,并对标准的可重复读隔离级别进行了优化,在很多情况下可以避免幻读问题。

# 2. 事务隔离级别的实现机制

# 2.1 MVCC (多版本并发控制)

MVCC 是 InnoDB 实现事务隔离的核心机制,它通过在每行数据后面保存两个隐藏的列来实现:

  • DB_TRX_ID:最近修改该行的事务 ID
  • DB_ROLL_PTR:指向该行的 undo log 记录

# 2.1.1 快照读与当前读

在 MVCC 机制下,InnoDB 将读操作分为两种:

  1. 快照读(Snapshot Read):读取的是记录的可见版本(有可能是历史版本),不加锁。例如:

    SELECT * FROM table WHERE id = 1;
    
    1
  2. 当前读(Current Read):读取的是记录的最新版本,并且会对记录加锁,保证其他事务不会并发修改这条记录。例如:

    SELECT * FROM table WHERE id = 1 FOR UPDATE;
    SELECT * FROM table WHERE id = 1 LOCK IN SHARE MODE;
    INSERT INTO table VALUES(1, 2);
    DELETE FROM table WHERE id = 1;
    UPDATE table SET col = 2 WHERE id = 1;
    
    1
    2
    3
    4
    5

# 2.1.2 InnoDB 如何实现 MVCC

InnoDB 通过 ReadView 机制来实现 MVCC。ReadView 主要包含以下内容:

  • m_ids:当前系统中活跃的(未提交的)事务 ID 集合
  • min_trx_id:活跃事务中最小的事务 ID
  • max_trx_id:系统应该分配给下一个事务的 ID,即当前系统中事务 ID 的上限值
  • creator_trx_id:创建 ReadView 的事务的 ID

对于一条记录,InnoDB 通过比较记录的 DB_TRX_ID 与 ReadView 中的信息来判断记录对当前事务是否可见:

  1. 如果 DB_TRX_ID < min_trx_id,说明该记录的事务在 ReadView 生成前已提交,记录可见
  2. 如果 DB_TRX_ID >= max_trx_id,说明该记录的事务在 ReadView 生成后才开始,记录不可见
  3. 如果 min_trx_id <= DB_TRX_ID < max_trx_id,需要判断 DB_TRX_ID 是否在 m_ids 中:
    • 如果在,说明该记录的事务还未提交,记录不可见
    • 如果不在,说明该记录的事务已提交,记录可见

如果记录不可见,就顺着 undo log 链找到可见的历史版本。

# 2.1.3 不同隔离级别下的 ReadView 创建时机

  • READ UNCOMMITTED:直接读取记录的最新版本,不使用 ReadView。
  • READ COMMITTED:每次 SELECT 都会创建一个新的 ReadView。
  • REPEATABLE READ:只在事务开始时创建一次 ReadView,整个事务期间都使用这个 ReadView。
  • SERIALIZABLE:使用锁机制,不使用 MVCC。

# 2.2 锁机制

InnoDB 使用的锁主要有:

  1. 共享锁(S 锁):允许持锁事务读取一行数据。
  2. 排他锁(X 锁):允许持锁事务更新或删除一行数据。
  3. 意向锁(IS/IX 锁):表级锁,表明事务想要在表中的某些行上加共享/排他锁。
  4. 记录锁(Record Lock):锁定单个行记录。
  5. 间隙锁(Gap Lock):锁定一个范围,但不包括记录本身。
  6. Next-Key Lock:Record Lock + Gap Lock,锁定记录及其前面的间隙。

锁类型示意图

# 3. 各隔离级别下的实现细节

# 3.1 READ UNCOMMITTED

读未提交是最低隔离级别,特点是:

  • 读取不需要加锁,写入需要加写锁,并且写锁会一直持有到事务结束。
  • 读取时不使用 MVCC,直接读取记录的最新版本,即使该版本还未提交。
  • 因此可能出现脏读、不可重复读和幻读。

# 3.2 READ COMMITTED

读已提交的特点是:

  • 读取操作仅加 MVCC 的快照读,不加锁。
  • 每次 SELECT 都会创建一个新的 ReadView。
  • 确保只能读到已经提交的数据,因此避免了脏读。
  • 但由于每次 SELECT 都会生成新的 ReadView,所以同一事务内的多次读取可能会得到不同的结果,即可能出现不可重复读和幻读。

# 3.3 REPEATABLE READ

可重复读是 InnoDB 的默认隔离级别,特点是:

  • 读取操作使用 MVCC 的快照读,只在事务开始时创建一次 ReadView。
  • 写操作使用记录锁(Record Lock)。
  • 确保同一事务多次读取同一记录的结果是一致的,避免了不可重复读。
  • InnoDB 通过 Next-Key Lock(记录锁+间隙锁)的方式解决大部分幻读问题。

# 3.3.1 InnoDB 如何解决幻读

标准的可重复读隔离级别是不能解决幻读问题的。但 InnoDB 实现的可重复读通过间隙锁(Gap Lock)和 Next-Key Lock 很大程度上避免了幻读:

  • 当使用唯一索引进行当前读操作时,InnoDB 只需要锁住记录本身
  • 当使用普通索引或者不使用索引时,InnoDB 会使用 Next-Key Lock 锁住记录及其间隙
  • 因此,在可重复读隔离级别下的当前读操作通常不会出现幻读

但需要注意,在某些特定情况下,可重复读隔离级别仍然可能出现幻读问题。例如,当事务A进行快照读(普通SELECT)查询,而事务B插入新记录并提交后,如果事务A再次进行快照读,不会看到新插入的记录(符合可重复读的要求)。但如果事务A随后进行当前读(例如SELECT FOR UPDATE),则可能会突然看到事务B插入的记录,这就产生了幻读。

# 3.4 SERIALIZABLE

串行化是最高的隔离级别,特点是:

  • 所有读取操作都会隐式加共享锁(S锁)
  • 本质上,将所有操作串行化执行,完全避免了并发问题
  • 由于串行执行事务,因此并发性能最差

# 4. 事务隔离级别的选择

在实际应用中,我们需要在数据一致性和性能之间做出平衡:

  • READ UNCOMMITTED:几乎不会被使用,因为它无法保证数据的一致性
  • READ COMMITTED:许多数据库的默认级别(如Oracle、SQL Server),适合于大多数应用场景
  • REPEATABLE READ:MySQL InnoDB 的默认级别,提供了更强的一致性保证,同时在InnoDB中通过Next-Key Lock很大程度上避免了幻读
  • SERIALIZABLE:在要求极高数据一致性且并发量不大的场景下使用

# 5. 隔离级别对性能的影响

隔离级别越高,需要加的锁就越多,并发性能就越差:

  • READ UNCOMMITTED:几乎不加锁,性能最好,但一致性最差
  • READ COMMITTED:只对记录加写锁,不加读锁,性能较好
  • REPEATABLE READ:加记录锁和间隙锁,性能中等
  • SERIALIZABLE:所有读写操作都加锁,性能最差

# 6. 一致性读与锁定读

InnoDB实现的一致性非锁定读(consistent nonlocking read)是通过MVCC实现的:

-- 一致性读(快照读)
SELECT * FROM table WHERE id = 1;

-- 锁定读(当前读)
SELECT * FROM table WHERE id = 1 FOR UPDATE;
SELECT * FROM table WHERE id = 1 LOCK IN SHARE MODE;
1
2
3
4
5
6

锁定读的使用场景:

  • 需要获取数据的最新版本时
  • 需要对数据进行修改前,防止其他事务同时修改
  • 实现悲观锁

# 7. 小结

事务隔离级别是数据库设计中的重要概念,它直接影响到数据的一致性和系统的并发性能:

  1. MySQL默认使用可重复读(REPEATABLE READ)隔离级别,它通过MVCC和锁机制的结合,在保证数据一致性的同时提供了较好的并发性能
  2. InnoDB的可重复读隔离级别通过Next-Key Lock机制解决了大部分幻读问题,这是对标准可重复读隔离级别的优化
  3. 在选择隔离级别时,需要根据应用场景在数据一致性和性能之间做出权衡
  4. 理解MVCC和锁机制对深入理解MySQL的事务隔离实现至关重要
#MySQL#事务隔离#MVCC#锁机制#幻读#学习笔记
上次更新: 6/21/2025
最近更新
01
表空间管理与回收
06-21
02
MySQL抖动刷脏页
06-21
03
count函数详解
06-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式