一句话定义
隔离级别(Isolation Level)是隔离强度的四级标尺——读未提交、读已提交、可重复读、串行化——每一级对应「允许/禁止哪些并发异常」(脏读、不可重复读、幻读、丢失更新)。
为什么重要
- 隔离级别选错不是性能问题而是正确性问题:资金、库存事故常源于在弱隔离下「想当然」的读改写。
- 各数据库同名级别行为不同(PG 的 RR 用 MVCC 无幻读,MySQL InnoDB 的 RR 靠间隙锁抑制幻读),不知道差异就会跨库迁移踩坑。
- 本知识点提供可复现实验:亲手造出每一种异常,比背表格牢固一个数量级。
前置知识
kp-014(事务生命周期、COMMIT/ROLLBACK)。
核心概念
- 脏读(Dirty Read):读到别人未提交的数据。
- 不可重复读(Non-repeatable Read):同事务两次读同一行,值被他人提交修改。
- 幻读(Phantom):同条件两次范围查询,出现新行。
- 丢失更新(Lost Update):两个事务读-改-写同一行,后写覆盖先写。
- 读未提交(Read Uncommitted):三者皆可能(除丢失更新)。
- 读已提交(Read Committed,RC):禁止脏读。
- 可重复读(Repeatable Read,RR):再禁止不可重复读。
- 串行化(Serializable):可串行化调度,上述全禁止。
原理与机制
SQL 标准用「允许的异常集」定义级别,Berenson 等人 1995 年指出标准定义的漏洞(幻读定义过窄),各实现自行取舍:PostgreSQL 的 RC 每条语句取新快照(语句级一致),RR 取事务首快照并用 SIRead 锁检测串行化异常;MySQL InnoDB 的 RC 每语句快照,RR 事务首快照且靠间隙锁(kp-016)阻止他人在扫描区间插入,从而抑制幻读。丢失更新不被标准四级显式覆盖,是 RR 下仍会发生的经典坑:两个事务都 SELECT balance 后各自 UPDATE,后写覆盖先写——解法是显式 SELECT ... FOR UPDATE 行锁、原子 UPDATE ... SET x = x - ? WHERE ...、或乐观版本号 WHERE version = ?。设置方式:MySQL SET SESSION TRANSACTION ISOLATION LEVEL ...,PG SET default_transaction_isolation = ...。
实例或案例
可复现实验:两会话 A/B,表 account(user_id INT PRIMARY KEY, balance INT),初始 (1, 1000),以下按行号交替执行(MySQL 默认 RR;PG 建议先测 RC 再切 RR 对比)。
-- 两会话 A/B,表: account(user_id INT PRIMARY KEY, balance INT)
-- 初始: (1, 1000)。以下按行号交替执行,MySQL 默认 RR / PG 建议先测 RC 再切 RR 对比
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- A1: BEGIN;
-- B1: BEGIN;
-- A2: UPDATE account SET balance = balance - 100 WHERE user_id = 1;
-- B2: SELECT balance FROM account WHERE user_id = 1; -- RC 下看到 1000(A 未提交,无脏读)
-- (切到 READ UNCOMMITTED 会看到 900:复现脏读)
-- A3: COMMIT;
-- B2'(重复读): SELECT balance FROM account WHERE user_id = 1; -- RC 下变成 900:复现不可重复读
-- (RR 下仍是 1000:快照可重复读)
-- B3: SELECT COUNT(*) FROM account WHERE balance > 500; -- 另一会话插入新行后再执行一次,
-- -- RC 下计数变化:复现幻读;RR/快照下不变
丢失更新实验:A、B 都在 RC 下 SELECT balance 得 1000,各自 UPDATE ... SET balance = 1000 - 100 与 - 200,最终余额 800 或 900 而非 700——后写覆盖先写。
常见误区
- 认为「InnoDB 默认 RR 所以没有幻读」:快照读无幻读,但
SELECT ... FOR UPDATE当前读场景需间隙锁配合;且 RR 允许丢失更新。 - 在弱隔离下做「读出来 → 内存判断 → 写回去」:任何跨语句的读改写都必须加锁或原子化,否则并发必丢更新。
- 盲目上串行化:PG 串行化会抛序列化失败需应用重试,未写重试逻辑的系统会大面积报错。
自测题
- RC 与 RR 的快照粒度差异是什么? 答:RC 每条语句开始时取新快照(能看到他事务新提交),RR 取事务第一条查询时的快照并贯穿全程。
- 为什么 RR 仍可能丢失更新?如何根治? 答:两个事务基于同一快照值各自覆盖写;根治手段是悲观行锁(SELECT FOR UPDATE)、原子自增式 UPDATE 或乐观并发(版本号条件更新+重试)。
公式或模型
本节不适用:隔离级别是异常集合的分级定义(四级别 × 四异常的矩阵),不是定量公式。
图示
本节不适用:本库以可执行实验代替时间线图——亲手跑一遍 A/B 会话交替脚本,异常出现的那一刻即是最好的图。
直观类比
隔离级别像会议室的隔音等级:读未提交是薄纱隔断(对方草稿都能看见);读已提交是普通墙(定稿才听见,但你说话时别人可以改文件);可重复读是套房(你开会期间房间内一切不变);串行化是排队使用同一间房。
与其他知识点的关系
kp-016 与 kp-017 是实现隔离的悲观/乐观两大路线;kp-018 讨论跨节点时隔离性的代价;kp-029 中「并发下丢库存」是最常见的业务化表达。
延伸阅读
- Berenson 等《A Critique of ANSI SQL Isolation Levels》(SIGMOD 1995)
- 《数据密集型应用系统设计》(Martin Kleppmann)第 7 章弱隔离级别