返回首页面试题

面试必备:MySQL 事务隔离级别详解

2026年03月25日9 min read

面试必备:MySQL 事务隔离级别详解

什么是事务?

事务是数据库操作的最小工作单元,要么全部成功,要么全部失败。

-- 转账场景
BEGIN;  -- 开启事务

UPDATE account SET balance = balance - 1000 WHERE id = 1;  -- 张三减1000
UPDATE account SET balance = balance + 1000 WHERE id = 2;  -- 李四加1000

COMMIT;  -- 提交
-- 如果中途失败,执行 ROLLBACK 回滚

事务四大特性(ACID)

特性说明保障机制
Atomic(原子性)全部成功或全部失败Undo Log
Consistency(一致性)事务前后数据状态一致其他特性保证
Isolation(隔离性)并发事务相互隔离锁 + MVCC
Durability(持久性)提交后数据持久保存Redo Log

并发问题

多个事务同时操作数据时,会产生问题:

1. 脏读(Dirty Read)

读到其他事务未提交的数据

时间线:        事务 A              事务 B
─────────────────────────────────────────────
T1              BEGIN;
T2              SELECT balance      BEGIN;
T3              WHERE id = 1;
T4              balance = 900       UPDATE balance = 800
T5              ← 看到 B 修改的值!
T6                                  ROLLBACK;  ← B 回滚了
T7              balance = 900       -- 但 A 已经读到脏数据
                ↑ 错误!数据根本不存在

2. 不可重复读(Non-repeatable Read)

同一事务中,两次读取同一行数据,结果不同(因为被其他事务修改了)。

时间线:        事务 A              事务 B
─────────────────────────────────────────────
T1              BEGIN;
T2              SELECT balance      BEGIN;
T3              WHERE id = 1;       
T4              balance = 1000      UPDATE balance = 900
T5              ← 1000             COMMIT;
T6              SELECT balance      
T7              WHERE id = 1;       
T8              ← 900              -- A 两次读到的值不一样!

3. 幻读(Phantom Read)

同一事务中,两次查询结果集不同(因为被其他事务插入了新行)。

时间线:        事务 A              事务 B
─────────────────────────────────────────────
T1              BEGIN;
T2              SELECT *            BEGIN;
T3              FROM user          
T4              WHERE age > 20;     INSERT INTO user ...
T5              ← 返回 5 条        COMMIT;
T6              SELECT *            
T7              FROM user          
T8              WHERE age > 20;     
T9              ← 返回 6 条        -- A 读到了"幻影"

脏读 vs 不可重复读 vs 幻读

问题原因读到的内容
脏读读了其他事务未提交的数据别人的修改
不可重复读其他事务修改了数据同一行数据变化
幻读其他事务插入/删除了数据结果集数量变化

四种隔离级别

MySQL 提供四种隔离级别,越严格性能越低:

隔离级别脏读不可重复读幻读说明
READ UNCOMMITTED可能可能可能最低,性能最高
READ COMMITTED不可能可能可能Oracle 默认
REPEATABLE READ不可能不可能可能MySQL 默认
SERIALIZABLE不可能不可能不可能最高,性能最低

1. READ UNCOMMITTED(读未提交)

最低级别,允许脏读。

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
SELECT balance FROM account WHERE id = 1;  -- 可能读到其他事务未提交的值
COMMIT;

2. READ COMMITTED(读已提交)

解决脏读,但可能有不可重复读。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
-- 只能读到已提交的数据
COMMIT;

3. REPEATABLE READ(可重复读)

MySQL 默认级别,解决脏读和不可重复读,但可能有幻读。

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
-- 事务中多次读取,结果一致
SELECT * FROM user;
SELECT * FROM user;  -- 和第一次结果一样
COMMIT;

MySQL 特殊:在可重复读下,MySQL 通过 MVCC + Next-Key Lock 基本解决了幻读。

4. SERIALIZABLE(串行化)

最高级别,完全串行执行,彻底解决问题,但性能最差。

SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
-- 所有读操作都会加锁
COMMIT;

MVCC(多版本并发控制)

核心思想

每个事务看到的是快照数据,而不是实时的最新数据。

同一时刻,两个事务可能看到不同的数据版本:

事务 A (id=1)   事务 B (id=2)   数据库
─────────────────────────────────────────
SELECT balance  SELECT balance  
← 1000         ← 1000          
                UPDATE balance = 900
                COMMIT;
SELECT balance  SELECT balance  
← 1000 (快照)   ← 900 (新版本)  

InnoDB 的 MVCC 实现

隐藏列

InnoDB 给每行数据添加了两个隐藏列:

┌──────────────────────────────────────────────┐
│                                              │
│  db_trx_id    │  db_roll_ptr  │  ...其他字段 │
│  (事务ID)     │  (回滚指针)    │              │
│                                              │
└──────────────────────────────────────────────┘
说明
db_trx_id最近修改这行的事务 ID
db_roll_ptr指向 undo log 的指针

Undo Log(版本链)

每行数据通过 roll_ptr 形成版本链:

                          事务 C 修改
                            ↓
    ┌─────────────────────────────────────────┐
    │  事务 A 插入: balance=1000              │
    │  trx_id=A, roll_ptr=null               │
    └──────────────┬──────────────────────────┘
                   │ roll_ptr
                   ▼
    ┌─────────────────────────────────────────┐
    │  事务 B 修改: balance=900               │
    │  trx_id=B, roll_ptr→A                   │
    └──────────────┬──────────────────────────┘
                   │ roll_ptr
                   ▼
    ┌─────────────────────────────────────────┐
    │  事务 C 修改: balance=800               │
    │  trx_id=C, roll_ptr→B                   │
    │  ← 最新版本                             │
    └─────────────────────────────────────────┘

ReadView(读视图)

事务开启时生成 ReadView,记录"当前有哪些事务还没提交"。

ReadView {
    m_ids: [活跃事务ID列表],
    min_trx_id: 最小活跃事务ID,
    max_trx_id: 最大事务ID,
    creator_trx_id: 当前事务ID
}

判断规则:
├─ trx_id < min_trx_id → 已提交,可以读
├─ trx_id >= max_trx_id → 未来事务,不可见
├─ trx_id in m_ids → 未提交事务,不可见
└─ trx_id == creator_trx_id → 自己,可以读

不同隔离级别的 MVCC

隔离级别读取时机
READ UNCOMMITTED最新版本(不加锁)
READ COMMITTED每次 SELECT 生成新 ReadView
REPEATABLE READ事务开始时生成 ReadView,一直用

当前读 vs 快照读

快照读(普通 SELECT)

SELECT * FROM user WHERE id = 1;  -- 快照读,读历史版本

当前读(加锁读取)

SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE;  -- 共享锁
SELECT * FROM user WHERE id = 1 FOR UPDATE;          -- 排他锁
INSERT / UPDATE / DELETE;                            -- 自动加排他锁

面试高频问题

Q1:MySQL 默认隔离级别是什么?

REPEATABLE READ

Oracle 默认是 READ COMMITTED

Q2:如何解决幻读?

方案 1:MVCC(解决快照读的幻读)

  • 快照读不会看到其他事务插入的数据

方案 2:Next-Key Lock(解决当前读的幻读)

  • 锁定索引范围 + 间隙,防止插入
SELECT * FROM user WHERE age > 20 FOR UPDATE;
-- 锁定 age > 20 的范围,包括间隙,阻止 INSERT age > 20

Q3:隔离级别设置在哪里?

-- 查看当前会话隔离级别
SELECT @@tx_isolation;

-- 查看全局隔离级别
SELECT @@global.tx_isolation;

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;

Q4:MVCC 能否完全避免幻读?

不能。MVCC 只解决快照读的幻读当前读仍可能幻读,但 Next-Key Lock 可以防止。


总结

四大隔离级别:
├─ READ UNCOMMITTED:最低,可能脏读
├─ READ COMMITTED:解决脏读,可能不可重复读
├─ REPEATABLE READ(MySQL默认):解决脏读+不可重复读
└─ SERIALIZABLE:最高,完全串行

并发问题:
├─ 脏读:读了别人未提交的数据
├─ 不可重复读:同一行数据两次读不一样
└─ 幻读:结果集数量变了

MVCC:
├─ 隐藏列:trx_id + roll_ptr
├─ Undo Log:版本链
├─ ReadView:判断版本可见性
└─ REPEATABLE READ:事务开始生成 ReadView

解决幻读:
├─ 快照读 → MVCC
└─ 当前读 → Next-Key Lock

评论区