🔒 数据库事务隔离级别

交互式图解 — 从并发问题到隔离原理,完整理解

🧩 什么是事务隔离级别?

先理解事务,再理解为什么需要隔离

💡 生活类比:银行柜台窗口

想象一家银行,多个柜员同时处理不同客户的账户操作。
如果没有任何隔离:柜员A正在帮客户A转账,柜员B同时读取了客户A的账户余额 —— 此时B看到的是"转账中"的脏数据,导致错误决策。

隔离级别就是数据库定义的一套规则,用来控制:
「当多个事务同时运行时,一个事务对另一个事务的可见程度

📦 什么是事务?

事务是一组操作的逻辑单元,满足 ACID 特性:

A 原子性 要么全做,要么全不做
C 一致性 前后数据合法
I 隔离性 并发互不干扰
D 持久性 提交后永久生效

🎯 隔离性解决什么问题?

多个事务并发执行时,如果不加控制,会出现三类异常读问题:

脏读:读到别人未提交的数据
不可重复读:同一行两次读值不同
幻读:两次查询行数不同

⚖️ 隔离 vs 性能的权衡

隔离级别越高,数据越安全,但并发性能越低(锁越多)。

这是一个经典的一致性 vs 性能取舍问题。
不同业务场景需要选择合适的隔离级别。

🛠️ MySQL 默认级别

MySQL InnoDB 默认使用 可重复读(RR) 隔离级别,并通过 MVCC + 间隙锁解决了幻读问题。

Oracle、PostgreSQL 等默认使用读已提交(RC)

隔离级别光谱 — 越往右越安全,越往左性能越高

READ
UNCOMMITTED
READ
COMMITTED
REPEATABLE
READ
SERIALIZABLE
← 性能高 / 风险大 安全 / 性能低 →

⚠️ 并发导致的三大异常问题

点击每个问题查看详细时序说明

🧟
脏读 Dirty Read
读到另一个事务尚未提交的数据修改
时序:
T=1: 事务A 将账户余额从 100 改为 50(未提交)
T=2: 事务B 读取余额,得到 50(脏数据!)
T=3: 事务A 回滚,余额恢复 100
T=4: 事务B 基于错误的 50 做了决策 → 数据错误

类比:你看到同事在草稿里写"辞职申请"就以为他要辞职,结果他只是在练字。

防止方法:隔离级别 ≥ READ COMMITTED 即可阻止脏读。
🔀
不可重复读 Non-Repeatable Read
同一事务内,两次读取同一行,值不同
时序:
T=1: 事务A 读取余额,得到 100
T=2: 事务B 将余额修改为 80 并 提交
T=3: 事务A 再次读取余额,得到 80(不一样了!)

注意:事务B已经提交了,所以不是脏读,但A在同一事务里读到了不同值。

类比:你查了一下银行余额是1000元,准备转账,转账前再查了一次变成800了——中间你老婆取了200。

防止方法:隔离级别 ≥ REPEATABLE READ。
👻
幻读 Phantom Read
同一事务内,两次范围查询,行数不同(多出或少了行)
时序:
T=1: 事务A 查询 age > 18 的用户,得到 5 条
T=2: 事务B 插入一条 age=20 的新用户并提交
T=3: 事务A 再次查询 age > 18,得到 6 条(多出来了!)

幻读 vs 不可重复读:
· 不可重复读针对已存在的行被修改/删除
· 幻读针对新插入的行导致集合变化

防止方法:SERIALIZABLE;MySQL RR 级别通过间隙锁(Gap Lock)防止幻读。
💀
丢失更新 Lost Update
两个事务同时读取-修改同一数据,其中一个的修改被覆盖
时序(经典 read-modify-write):
T=1: 事务A 读取库存 = 10
T=2: 事务B 读取库存 = 10
T=3: 事务A 库存 -1,写入 9,提交
T=4: 事务B 库存 -1(基于旧值10),写入 9,提交
结果:库存 = 9,但应该是 8!少了一次扣减。

防止方法:乐观锁(version号)或 SELECT ... FOR UPDATE 悲观锁。

📊 各隔离级别能防止哪些问题?

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED ✗ 可能 ✗ 可能 ✗ 可能
READ COMMITTED ✓ 防止 ✗ 可能 ✗ 可能
REPEATABLE READ ✓ 防止 ✓ 防止 ✗ 可能*
SERIALIZABLE ✓ 防止 ✓ 防止 ✓ 防止

* MySQL InnoDB 的 RR 级别通过 Next-Key Lock(间隙锁)在很大程度上防止了幻读

📚 四种隔离级别详解

SQL 标准定义了4个级别,逐级增强

READ UNCOMMITTED 读未提交 · 最低级别

原理:事务可以读取其他事务尚未提交的数据变更。
实现:不加任何读锁,直接读最新版本(最新的 undo log 或 buffer pool 数据)。
问题:脏读、不可重复读、幻读都可能发生。
使用场景:极少使用,仅在能接受脏数据且需要极致性能时考虑(如日志统计、粗略计数)。

READ COMMITTED 读已提交 · Oracle/PG 默认

原理:只能读取其他事务已提交的数据,杜绝脏读。
实现(MVCC):每次 SELECT 都生成一个新的读视图(Read View),只能看到创建视图前已提交的版本。
问题:两次读取间隔内其他事务提交了修改,导致不可重复读。
使用场景:互联网业务常用,如电商、社交类(对一致性要求不极端严格)。

REPEATABLE READ 可重复读 · MySQL InnoDB 默认 ⭐

原理:同一事务内,多次读取同一行结果一致,杜绝不可重复读。
实现(MVCC):事务开始时生成一个固定的 Read View,整个事务内都用这个快照读取。
防幻读:MySQL 额外使用 Next-Key Lock(行锁 + 间隙锁)防止其他事务在范围内插入新行。
使用场景:银行转账、库存扣减等需要强一致性的业务。

SERIALIZABLE 串行化 · 最高级别

原理:所有事务串行执行,完全隔离,等同于单线程。
实现:所有 SELECT 加共享锁(S锁),所有 INSERT/UPDATE/DELETE 加排他锁(X锁),大量锁等待。
优点:完全杜绝所有并发异常。
缺点:性能极差,容易死锁,并发度几乎为0。
使用场景:金融交易核心账务、必须强一致性的场合,实际很少用。

🎬 并发场景时序演示

选择隔离级别,观察两个事务并发时的不同表现

选择演示场景:

选择隔离级别:

READ UNCOMMITTED 🧟 脏读场景 时间轴 ↓

🔬 MVCC 原理深度解析

Multi-Version Concurrency Control — 多版本并发控制

MVCC 是 MySQL InnoDB 实现隔离级别的核心机制。它的核心思想是:读写不互斥 — 读不加锁,写通过版本链隔离,从而大幅提升并发性能。

每行数据背后维护一条版本链(Undo Log Chain),每次修改产生新版本,旧版本保留供事务快照读取。

📌 版本链结构示意

当前行 row data balance = 300 trx_id = 300 roll_ptr undo log v2 balance = 200 trx_id = 200 roll_ptr undo log v1 balance = 100 trx_id = 100 NULL Read View 事务A (trx_id=150) m_up_limit_id = 150 RR 模式:只能读到 v1 (trx_id<150) 事务B (trx_id=250) m_up_limit_id = 250 RC 模式:最新可见 v2

📖 Read View 包含什么?

  • m_ids:创建时活跃的事务ID列表
  • min_trx_id:活跃事务中最小ID
  • max_trx_id:下一个将分配的事务ID
  • creator_trx_id:创建此 Read View 的事务ID

📏 版本可见性判断规则

  • • trx_id == creator → 可见(自己修改的)
  • • trx_id < min_trx_id → 可见(已提交)
  • • trx_id ≥ max_trx_id → 不可见(事后开始)
  • • trx_id in m_ids → 不可见(仍活跃)
  • • 否则 → 可见

⚡ RC 和 RR 的本质区别

READ COMMITTED
每次 SELECT 执行时
都创建新的 Read View
→ 能看到"上一秒"别人提交的数据
→ 两次读到不同值 = 不可重复读
REPEATABLE READ
事务开始时创建一次 Read View
整个事务期间复用同一个快照
→ 别人的提交对我不可见
→ 两次读到相同值 = 可重复读

📊 四级隔离全面对比

点击卡片查看详细信息

READ UNCOMMITTED
读未提交
性能最高
安全最低
READ COMMITTED
读已提交
性能较高
安全中等
REPEATABLE READ
可重复读 ⭐MySQL默认
性能中等
安全较高
SERIALIZABLE
串行化
性能最低
安全最高

🛠️ 如何设置隔离级别

-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置会话级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置全局级别
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 或在 my.cnf 中配置
transaction-isolation = REPEATABLE-READ

💡 如何选择隔离级别?

🏦 金融/银行核心 → SERIALIZABLE 或 RR + FOR UPDATE
🛒 电商库存扣减 → REPEATABLE READ + 乐观锁
📱 互联网 OLTP → READ COMMITTED(Oracle/PG 默认)
📊 OLAP 分析查询 → READ COMMITTED
🔢 实时统计近似值 → READ UNCOMMITTED
🐬 MySQL 通用业务 → REPEATABLE READ(默认)