前置知识: SQL

隔离级别

00:00
5 min Advanced 2026/6/14

SQL事务隔离级别:读未提交、读已提交、可重复读、序列化的原理、实现机制与选择策略

1. 隔离级别概述

事务隔离级别定义了一个事务必须与其它事务隔离的程度。更高的隔离级别提供更强的一致性保证,但并发性能更低。

1.1 SQL 标准隔离级别

级别脏读不可重复读幻读性能
READ UNCOMMITTED最高
READ COMMITTED
REPEATABLE READ*
SERIALIZABLE最低

*MySQL InnoDB 的 REPEATABLE READ 通过 Next-Key Lock 在一定程度上防止幻读。

2. READ UNCOMMITTED(读未提交)

2.1 定义

一个事务可以读取另一个事务未提交的修改(脏读)。

-- 事务A
BEGIN;
UPDATE accounts SET balance = balance + 1000 WHERE id = 1;
-- 未提交

-- 事务B(READ UNCOMMITTED)
BEGIN ISOLATION LEVEL READ UNCOMMITTED;
SELECT balance FROM accounts WHERE id = 1;
-- 读到事务A未提交的修改(脏读)

-- 事务A回滚
ROLLBACK;
-- 事务B读到的数据是无效的

2.2 脏读问题

时间线:
T1: BEGIN; UPDATE accounts SET balance = 2000 WHERE id = 1;
T2:                                SELECT balance → 2000 (脏读)
T1: ROLLBACK; (balance 恢复为 1000)
T2: 基于 2000 做决策 → 错误!

2.3 使用场景

  • 几乎不使用,仅用于监控和调试
  • 对数据准确性无要求的场景(如近似统计)

3. READ COMMITTED(读已提交)

3.1 定义

一个事务只能读取其他事务已提交的修改,解决了脏读问题。

-- PostgreSQL 默认隔离级别
-- Oracle 默认隔离级别

-- 事务A
BEGIN;
UPDATE accounts SET balance = 2000 WHERE id = 1;
-- 未提交

-- 事务B
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT balance FROM accounts WHERE id = 1;
-- 返回 1000(事务A未提交的修改不可见)

-- 事务A提交
COMMIT;

-- 事务B再次读取
SELECT balance FROM accounts WHERE id = 1;
-- 返回 2000(事务A已提交,现在可见)
-- 两次读取结果不同 → 不可重复读

3.2 不可重复读问题

时间线:
T1: BEGIN ISOLATION LEVEL READ COMMITTED;
T1: SELECT balance FROM accounts WHERE id = 1; → 1000
T2:                                UPDATE accounts SET balance = 2000 WHERE id = 1; COMMIT;
T1: SELECT balance FROM accounts WHERE id = 1; → 2000 (不可重复读)

3.3 实现机制

MVCC(PostgreSQL)

  • 每次 SELECT 创建新的快照(Snapshot)
  • 快照包含:所有已提交事务的可见性信息
  • 同一事务内两次 SELECT 可能看到不同数据

锁机制(SQL Server)

  • 读取时获取共享锁,读取后立即释放
  • 写入时获取排他锁,持有到事务结束

4. REPEATABLE READ(可重复读)

4.1 定义

同一事务内多次读取同一行数据,结果一致,解决了不可重复读问题。

-- MySQL InnoDB 默认隔离级别

-- 事务A
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE id = 1; → 1000

-- 事务B
UPDATE accounts SET balance = 2000 WHERE id = 1;
COMMIT;

-- 事务A再次读取
SELECT balance FROM accounts WHERE id = 1; → 1000 (可重复读)

4.2 幻读问题

-- 事务A
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT COUNT(*) FROM accounts WHERE balance > 1500; → 0

-- 事务B
INSERT INTO accounts (id, balance) VALUES (2, 2000);
COMMIT;

-- 事务A再次查询
SELECT COUNT(*) FROM accounts WHERE balance > 1500; → 0 (MVCC下无幻读)
-- 但如果事务A执行更新:
UPDATE accounts SET balance = balance + 100 WHERE balance > 1500;
-- 影响了事务B插入的行!
SELECT COUNT(*) FROM accounts WHERE balance > 1500; → 1 (幻读)

4.3 MySQL InnoDB 的幻读防护

InnoDB 通过 Next-Key Lock(记录锁 + 间隙锁)防止幻读:

-- 事务A
BEGIN;
SELECT * FROM employees WHERE dept_id = 5 FOR UPDATE;
-- 锁定 dept_id = 5 的所有行及间隙

-- 事务B
INSERT INTO employees (name, dept_id) VALUES ('new', 5);
-- 被阻塞!无法插入 dept_id = 5 的新行

4.4 实现机制

MVCC(PostgreSQL)

  • 事务开始时创建快照,整个事务使用同一快照
  • 快照决定哪些行版本可见

锁 + MVCC(MySQL InnoDB)

  • 快照读(普通 SELECT):使用 MVCC,基于事务开始时的快照
  • 当前读(SELECT FOR UPDATE/LOCK IN SHARE MODE):使用 Next-Key Lock

5. SERIALIZABLE(可串行化)

5.1 定义

最高隔离级别,事务执行效果等同于某种串行执行顺序,完全消除并发异常。

BEGIN ISOLATION LEVEL SERIALIZABLE;
-- 所有操作如同串行执行

5.2 实现机制

两阶段锁(2PL)

  • 事务分为增长阶段(获取锁)和收缩阶段(释放锁)
  • 所有事务结束时统一释放

串行化快照隔离(SSI)

  • PostgreSQL 使用 SSI 实现 SERIALIZABLE
  • 基于 MVCC检测读写冲突
  • 检测到危险结构时,回滚其中一个事务
-- PostgreSQL SSI 示例
-- 事务A
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT balance FROM accounts WHERE id = 1;  -- 读取

-- 事务B
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT balance FROM accounts WHERE id = 2;  -- 读取

-- 事务A
UPDATE accounts SET balance = balance - 100 WHERE id = 2;  -- 写入事务B读取的行

-- 事务B
UPDATE accounts SET balance = balance - 100 WHERE id = 1;  -- 写入事务A读取的行

-- 事务A
COMMIT;  -- 成功

-- 事务B
COMMIT;  -- 错误!检测到序列化冲突,回滚
-- ERROR: could not serialize access due to read/write dependencies

5.3 SERIALIZABLE 的代价

  • 性能:并发最低,事务冲突回滚
  • 死锁范围大,死锁增加
  • 适用场景一致性要求的场景

6. 隔离级别选择

6.1 各数据库默认隔离级别

数据库默认隔离级别
PostgreSQLREAD COMMITTED
MySQLREPEATABLE READ
OracleREAD COMMITTED
SQL ServerREAD COMMITTED
SQLiteSERIALIZABLE

6.2 选择建议

场景推荐隔离级别理由
查询READ COMMITTED能看到最新已提交数据
OLTP 交易REPEATABLE READ防止不可重复读
金融SERIALIZABLE一致性
日志写入READ COMMITTED吞吐,允许不可重复读

6.3 应用层加锁替代高隔离级别

-- 使用 SELECT FOR UPDATE 实现行级串行化
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- 应用逻辑检查
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- 比全局 SERIALIZABLE 更高效,只锁定需要的行

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式