💻CS 数据库

事务与隔离级别

难度:⭐⭐ | 高频指数:🔥🔥🔥

面试回答

常见问法

  • 事务的 ACID 特性是什么?
  • 四种隔离级别分别是什么?
  • 脏读、不可重复读、幻读有什么区别?
  • MySQL 默认隔离级别是什么?怎么实现的?
  • MVCC 是什么?怎么工作的?

回答

ACID 四大特性:

特性含义实现方式
原子性(Atomicity)事务要么全做要么全不做undo log(回滚日志)
一致性(Consistency)事务前后数据满足约束由其他三个特性保证
隔离性(Isolation)并发事务互不干扰锁 + MVCC
持久性(Durability)提交后数据永久保存redo log(重做日志)

四种隔离级别(从低到高):

隔离级别脏读不可重复读幻读
读未提交(Read Uncommitted)
读已提交(Read Committed)
可重复读(Repeatable Read)✓(InnoDB 通过间隙锁解决)
串行化(Serializable)

MySQL InnoDB 默认隔离级别是可重复读(RR),并且通过 MVCC + 间隙锁在很大程度上解决了幻读问题。

追问

1. 脏读、不可重复读、幻读的区别?

  • 脏读:读到了其他事务未提交的数据。如果那个事务回滚了,你读到的就是无效数据。
  • 不可重复读:同一事务内两次读同一行,结果不同(被其他事务修改了)。
  • 幻读:同一事务内两次范围查询,结果集行数不同(被其他事务插入/删除了)。

2. MVCC 是什么?

MVCC(Multi-Version Concurrency Control,多版本并发控制)让读操作不加锁,通过保存数据的多个版本来实现。每行数据有隐藏的版本号(事务 ID),读操作根据自己的”快照”决定能看到哪个版本。

3. Read View 是什么?

Read View 是事务在某个时刻的”快照”,记录了当时活跃的事务 ID 列表。通过 Read View 判断某个版本的数据对当前事务是否可见。


原理展开

1. 三种并发问题详解

-- 脏读示例
-- 事务 A                          事务 B
BEGIN;                             BEGIN;
UPDATE accounts SET balance=0      
  WHERE id=1;                      
                                   SELECT balance FROM accounts 
                                     WHERE id=1;  -- 读到 0(未提交)
ROLLBACK;                          -- 事务 A 回滚了,但 B 已经用了错误数据
-- 不可重复读示例
-- 事务 A                          事务 B
BEGIN;                             BEGIN;
SELECT balance FROM accounts       
  WHERE id=1;  -- 读到 100         
                                   UPDATE accounts SET balance=200
                                     WHERE id=1;
                                   COMMIT;
SELECT balance FROM accounts       
  WHERE id=1;  -- 读到 200(变了!)
-- 幻读示例
-- 事务 A                          事务 B
BEGIN;                             BEGIN;
SELECT * FROM users                
  WHERE age > 20;  -- 3 行         
                                   INSERT INTO users(age) VALUES(25);
                                   COMMIT;
SELECT * FROM users                
  WHERE age > 20;  -- 4 行(多了一行!)

2. MVCC 实现原理

InnoDB 每行数据的隐藏列:

  • DB_TRX_ID:最后修改该行的事务 ID
  • DB_ROLL_PTR:指向 undo log 中该行的上一个版本
  • DB_ROW_ID:隐藏主键(没有主键时使用)

版本链:

当前版本 → undo log 版本1 → undo log 版本2 → ...
(trx_id=5)  (trx_id=3)      (trx_id=1)

3. Read View 的可见性判断

Read View 包含:

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

判断规则:

对于某行数据的 trx_id:
1. trx_id < min_trx_id → 可见(事务在 Read View 创建前已提交)
2. trx_id >= max_trx_id → 不可见(事务在 Read View 创建后才开始)
3. min_trx_id <= trx_id < max_trx_id:
   - trx_id 在 m_ids 中 → 不可见(事务还未提交)
   - trx_id 不在 m_ids 中 → 可见(事务已提交)
4. trx_id == creator_trx_id → 可见(自己的修改)

4. RC 和 RR 的 MVCC 区别

Read Committed(RC):

  • 每次 SELECT 都创建新的 Read View
  • 所以能看到其他事务已提交的最新数据
  • 解决脏读,但有不可重复读

Repeatable Read(RR):

  • 只在事务第一次 SELECT 时创建 Read View
  • 后续 SELECT 复用同一个 Read View
  • 所以同一事务内看到的数据一致
  • 解决脏读和不可重复读

5. undo log 和 redo log

undo log(回滚日志):

  • 记录数据修改前的值
  • 用于事务回滚(保证原子性)
  • 用于 MVCC(提供历史版本)
  • INSERT 对应的 undo log 在事务提交后可以立即删除
  • UPDATE/DELETE 的 undo log 需要等没有事务引用时才能删除

redo log(重做日志):

  • 记录数据页的物理修改
  • 用于崩溃恢复(保证持久性)
  • WAL(Write-Ahead Logging):先写日志再写数据页
  • 顺序写入,性能高

6. 事务的实现流程

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

内部流程:
1. 在 undo log 中记录旧值(balance = 原值)
2. 修改 Buffer Pool 中的数据页
3. 在 redo log buffer 中记录修改
4. 事务提交时:redo log 刷盘(fsync)
5. 数据页异步刷盘(checkpoint)

崩溃恢复:
- 如果 redo log 中有该事务的 commit 记录 → 重做
- 如果没有 commit 记录 → 用 undo log 回滚

7. 隔离级别的选择

场景推荐隔离级别原因
大部分业务RR(MySQL 默认)平衡一致性和性能
对一致性要求不高RC性能更好,锁冲突少
金融转账RR 或 Serializable必须保证一致性
报表统计RC不需要可重复读

互联网公司常用 RC 的原因:

  • 锁粒度更小(没有间隙锁),并发性能更好
  • 配合乐观锁/业务层校验保证一致性
  • 阿里巴巴等公司的 MySQL 规范推荐 RC

易错点

  • 说”ACID 中的 C 是由数据库保证的”——一致性更多是应用层的责任,数据库通过 AID 来支撑。
  • 混淆不可重复读和幻读——不可重复读是同一行数据变了,幻读是结果集行数变了。
  • 说”RR 完全解决了幻读”——MVCC 的快照读解决了,但当前读(SELECT … FOR UPDATE)仍可能幻读,需要间隙锁。
  • 不知道 MVCC 的实现——只说”多版本”不够,要能说出 Read View 和版本链。
  • 说”隔离级别越高越好”——越高并发性能越差,要根据业务选择。
  • 混淆 undo log 和 redo log 的作用——undo 保证原子性(回滚),redo 保证持久性(恢复)。

记忆技巧

  • ACID 口诀:原(原子)一(一致)隔(隔离)持(持久)
  • 三种问题:脏(读未提交的)、不重复(同行变了)、幻(行数变了)
  • 隔离级别从低到高:RU → RC → RR → S
  • MVCC 核心:版本链 + Read View + 可见性判断
  • 两个 log:undo 管回滚,redo 管恢复
  • RC vs RR 的 MVCC 区别:RC 每次读新快照,RR 复用第一次的快照

面试速答版

事务的 ACID:原子性(undo log 回滚)、一致性(由 AID 保证)、隔离性(MVCC + 锁)、持久性(redo log)。四种隔离级别从低到高:读未提交、读已提交、可重复读、串行化。脏读是读到未提交数据,不可重复读是同行数据变了,幻读是结果集行数变了。MySQL 默认 RR,通过 MVCC 实现:每行有版本链(undo log),事务通过 Read View 判断哪个版本可见。RC 每次 SELECT 创建新 Read View,RR 复用第一次的。InnoDB 在 RR 下通过间隙锁进一步解决幻读。

Related · 数据库