事务隔离级别深度解析:PostgreSQL vs MySQL
事务隔离级别是数据库并发控制的核心概念,直接影响系统的数据一致性与并发性能。SQL 标准(SQL-92)定义了四种隔离级别,但 PostgreSQL 和 MySQL(InnoDB)在实现细节上存在显著差异,理解这些差异是写出正确、高效 SQL 的关键。
一、并发问题:为什么需要隔离级别?
在多事务并发执行时,可能出现以下四类问题:
1. 脏读(Dirty Read)
事务 A 读取了事务 B 尚未提交的数据。若 B 回滚,A 读到的就是”幻觉数据”。
-- 事务 B(未提交) |
2. 不可重复读(Non-Repeatable Read)
事务 A 两次读取同一行,期间事务 B 修改并提交了该行,导致两次结果不同。
-- 事务 A 第一次读 |
3. 幻读(Phantom Read)
事务 A 两次执行范围查询,期间事务 B 插入了符合条件的新行,导致两次结果行数不同。
-- 事务 A 第一次查 |
4. 序列化异常(Serialization Anomaly)
多个事务并发执行的结果,无法等价于这些事务按某种顺序串行执行的结果。这是最高级别的并发问题,只有 SERIALIZABLE 能完全防止。
二、SQL 标准定义的四种隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 序列化异常 |
|---|---|---|---|---|
| READ UNCOMMITTED | ✅ 可能 | ✅ 可能 | ✅ 可能 | ✅ 可能 |
| READ COMMITTED | ❌ 防止 | ✅ 可能 | ✅ 可能 | ✅ 可能 |
| REPEATABLE READ | ❌ 防止 | ❌ 防止 | ✅ 可能 | ✅ 可能 |
| SERIALIZABLE | ❌ 防止 | ❌ 防止 | ❌ 防止 | ❌ 防止 |
✅ = 该问题可能发生 ❌ = 该问题被防止
三、PostgreSQL 的隔离级别实现
PostgreSQL 基于 MVCC(多版本并发控制) 实现隔离,每个事务看到的是数据的一个一致性快照,读操作不阻塞写操作,写操作不阻塞读操作。
默认隔离级别:READ COMMITTED
-- 查看当前隔离级别 |
PostgreSQL 的四个级别行为如下:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 序列化异常 |
|---|---|---|---|---|
| READ UNCOMMITTED | ❌ 防止¹ | ✅ 可能 | ✅ 可能 | ✅ 可能 |
| READ COMMITTED(默认) | ❌ 防止 | ✅ 可能 | ✅ 可能 | ✅ 可能 |
| REPEATABLE READ | ❌ 防止 | ❌ 防止 | ❌ 防止² | ✅ 可能 |
| SERIALIZABLE | ❌ 防止 | ❌ 防止 | ❌ 防止 | ❌ 防止 |
¹ PostgreSQL 不真正支持 READ UNCOMMITTED,设置后实际按 READ COMMITTED 处理。
² PostgreSQL 的 REPEATABLE READ 额外防止了幻读,这比 SQL 标准要求更严格。
各级别详解
READ COMMITTED
每条 SQL 语句看到的是语句开始时已提交的数据快照。
BEGIN; -- 默认 READ COMMITTED |
REPEATABLE READ
整个事务看到的是事务开始时的数据快照,且 PostgreSQL 实现中额外防止了幻读。
BEGIN ISOLATION LEVEL REPEATABLE READ; |
⚠️ 注意:在 REPEATABLE READ 下,若尝试更新另一事务已修改的行,PostgreSQL 会检测到写-写冲突并报错:
ERROR: could not serialize access due to concurrent update |
SERIALIZABLE
基于 SSI(Serializable Snapshot Isolation) 算法,检测序列化异常并在检测到危险模式时中止事务,抛出错误码 40001。
BEGIN ISOLATION LEVEL SERIALIZABLE; |
应用层必须实现重试逻辑:
function withSerializable(PDO $pdo, callable $fn): mixed |
四、MySQL(InnoDB)的隔离级别实现
MySQL InnoDB 同样使用 MVCC,但在锁机制上有所不同,尤其在 REPEATABLE READ 级别下依赖间隙锁(Gap Lock) 防止幻读。
默认隔离级别:REPEATABLE READ
-- 查看当前隔离级别 |
MySQL 四个级别行为如下:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 备注 |
|---|---|---|---|---|
| READ UNCOMMITTED | ✅ 可能 | ✅ 可能 | ✅ 可能 | 几乎不使用 |
| READ COMMITTED | ❌ 防止 | ✅ 可能 | ✅ 可能 | 常用于高并发写入 |
| REPEATABLE READ(默认) | ❌ 防止 | ❌ 防止 | ⚠️ 部分防止³ | InnoDB 默认 |
| SERIALIZABLE | ❌ 防止 | ❌ 防止 | ❌ 防止 | 所有 SELECT 加共享锁 |
³ MySQL
REPEATABLE READ下,普通SELECT(快照读)不会产生幻读,但当前读(SELECT ... FOR UPDATE/SELECT ... LOCK IN SHARE MODE)需要间隙锁才能防止幻读。
MySQL 的锁机制补充
MySQL 在 REPEATABLE READ 下通过三种锁防止幻读:
-- 1. 记录锁(Record Lock):锁定具体行 |
⚠️ 间隙锁在高并发写入场景容易造成死锁,可通过将隔离级别改为 READ COMMITTED 来禁用间隙锁:
-- 会话级别修改(适合高并发写入场景) |
五、PostgreSQL vs MySQL 核心差异对比
差异总览
| 对比维度 | PostgreSQL | MySQL (InnoDB) |
|---|---|---|
| 默认隔离级别 | READ COMMITTED | REPEATABLE READ |
| REPEATABLE READ 防幻读 | ✅ 是(MVCC 快照) | ⚠️ 部分(需加锁) |
| READ UNCOMMITTED | 实际按 READ COMMITTED 处理 | 真正允许脏读 |
| SERIALIZABLE 实现 | SSI(乐观,可能失败重试) | 悲观锁(SELECT 加共享锁) |
| 幻读防止机制 | MVCC 快照 | 间隙锁(Gap Lock) |
| 写-写冲突处理 | 后写者等待或失败 | 后写者等待(行锁) |
| 锁粒度 | 行级 + 表级 | 行级(记录锁/间隙锁/临键锁) |
关键差异详解
差异一:REPEATABLE READ 对幻读的处理
这是最容易踩坑的差异点。
-- ===== PostgreSQL REPEATABLE READ ===== |
结论:MySQL 的 REPEATABLE READ 在当前读场景下不能防幻读,PostgreSQL 的 REPEATABLE READ 在所有场景都防幻读。
差异二:SERIALIZABLE 实现方式
-- PostgreSQL SERIALIZABLE:乐观策略,事务可以并发执行 |
差异三:写-写冲突处理
-- 场景:事务 A 和事务 B 同时要修改同一行 |
六、实战选择指南
PostgreSQL 隔离级别选择
-- ✅ 场景 1:普通查询、报表统计(无需高一致性) |
MySQL 隔离级别选择
-- ✅ 场景 1:高并发写入、日志记录(避免间隙锁死锁) |
七、常见误区与注意事项
误区一:认为提高隔离级别总是更安全
更高的隔离级别会增加锁竞争(MySQL)或事务失败重试率(PostgreSQL),在高并发系统中可能反而导致性能骤降。应按实际需求选择最低满足要求的隔离级别。
误区二:PostgreSQL 和 MySQL 的 REPEATABLE READ 等价
这是最常见的迁移陷阱! 如上文所述,MySQL 的 REPEATABLE READ 在当前读下不防幻读,PostgreSQL 则完全防幻读。从 MySQL 迁移到 PostgreSQL 时需要重新评估隔离级别需求。
误区三:忽略序列化失败的重试
PostgreSQL SERIALIZABLE 隔离级别必须配合应用层重试逻辑,否则序列化冲突会直接返回错误给用户,导致业务失败。
误区四:在 MySQL 高并发写入时使用 REPEATABLE READ
MySQL REPEATABLE READ 下的间隙锁在并发 INSERT 时极易产生死锁,对于日志表、消息队列等高写入场景,推荐降级为 READ COMMITTED。
快速排查命令
-- ===== PostgreSQL ===== |
八、总结
| PostgreSQL | MySQL | |
|---|---|---|
| 默认级别 | READ COMMITTED | REPEATABLE READ |
| 推荐日常使用 | READ COMMITTED | REPEATABLE READ |
| 高并发写入 | READ COMMITTED | READ COMMITTED |
| 账务/余额操作 | REPEATABLE READ | REPEATABLE READ + FOR UPDATE |
| 严格金融场景 | SERIALIZABLE(需重试) | SERIALIZABLE(性能差) |
| 迁移注意点 | RR 防幻读更彻底 | 当前读需额外加锁防幻读 |
💡 一句话总结:PostgreSQL 的 MVCC 快照在
REPEATABLE READ就能防幻读,而 MySQL 需要FOR UPDATE间隙锁配合;PostgreSQL 的SERIALIZABLE是乐观的(可能失败重试),MySQL 是悲观的(加共享锁)。理解这两点,就能避免 90% 的隔离级别踩坑。





