PostgreSQL中的脏读(Dirty Read)、不可重复读(Non-repeatable Read)和幻读(Phantom Read)。
在关系型数据库(包括 PostgreSQL)中,脏读(Dirty Read)、不可重复读(Non-repeatable Read)和幻读(Phantom Read)是多个事务并发执行时可能出现的数据不一致现象。
为了解决这些问题,SQL 标准定义了四种事务隔离级别。不过,PostgreSQL 的实现依赖于多版本并发控制(MVCC),因此它的实际表现与标准 SQL 规范有一些非常重要的区别。
下面为您详细解释这三种现象以及它们在 PostgreSQL 中的具体表现。
1. 脏读(Dirty Read)
定义:
一个事务读取了另一个事务尚未提交(Uncommitted)的修改数据。如果那个未提交的事务最终回滚了,那么第一个事务读取到的数据就是无效的“脏”数据。
场景举例:
- 事务 A 将某用户的余额从 100 修改为 200(未提交)。
- 事务 B 读取该用户的余额,得到了 200。
- 事务 A 发生错误,执行了回滚(Rollback),实际余额恢复为 100。
- 事务 B 拿着 200 的余额去进行后续逻辑处理,导致严重的数据错误。
在 PostgreSQL 中的表现:
- 绝对不可能发生。
- PostgreSQL 基于 MVCC(多版本并发控制)机制,任何事务只能看到已经提交的数据。
- 即使你在 PostgreSQL 中显式设置隔离级别为最低的
READ UNCOMMITTED,它的实际行为也会自动提升为READ COMMITTED。因此,在 PostgreSQL 中不存在脏读。
2. 不可重复读(Non-repeatable Read)
定义:
在一个事务内,多次读取同一行数据,结果却不一致。这是因为在两次读取之间,另一个事务修改或删除了该行数据并提交了。
(侧重于对原有数据的 UPDATE 或 DELETE)
场景举例:
- 事务 A 查询了员工小明的工资,发现是 5000。
- 事务 B 将小明的工资修改为 6000,并提交了事务。
- 事务 A 再次查询小明的工资,发现变成了 6000。
- 事务 A 在同一个事务中,两次读取同一行数据得到了不同的结果。
在 PostgreSQL 中的表现:
- 在默认隔离级别
READ COMMITTED(读已提交) 下,会发生不可重复读。因为每次执行SELECT时,PostgreSQL 都会获取一个新的数据快照。 - 如果将隔离级别提升为
REPEATABLE READ(可重复读),则不会发生。在这个级别下,事务在第一次查询时建立一个快照,整个事务期间只能看到那个快照时的数据状态,忽略其他事务后续的修改。
3. 幻读(Phantom Read)
定义:
在一个事务内,多次执行同一个范围查询(带有 WHERE 条件),返回的记录行数不一致。这是因为在两次查询之间,另一个事务插入(或删除)了符合该条件的新行并提交了。
(侧重于 INSERT 带来的数据集数量的变化)
场景举例:
- 事务 A 查询
WHERE age > 18的用户,查到了 5 条记录。 - 事务 B 插入了一条
age = 20的新用户记录,并提交了事务。 - 事务 A 再次查询
WHERE age > 18的用户,发现查到了 6 条记录。就好像出现了“幻觉”一样,多出了一条数据。
在 PostgreSQL 中的表现(重要区别):
- 在 SQL 标准中,幻读在
REPEATABLE READ级别下是允许发生的。 - 但在 PostgreSQL 中,
REPEATABLE READ级别下是不会发生幻读的! - 同样得益于 MVCC,PostgreSQL 的
REPEATABLE READ使用的是快照隔离(Snapshot Isolation)。事务看到的是事务开始那一刻的整个数据库快照,其他事务后来插入的数据在这个快照中根本不存在。 - 因此,在 PostgreSQL 中,幻读只会在默认的
READ COMMITTED级别下发生。
总结:PostgreSQL 隔离级别对照表
为了直观对比,我们可以看下标准 SQL 与 PostgreSQL 实际表现的差异:
| 隔离级别 (Isolation Level) | 脏读 (Dirty Read) | 不可重复读 (Non-repeatable Read) | 幻读 (Phantom Read) | 序列化异常 (Serialization Anomaly) |
|---|---|---|---|---|
| Read uncommitted | 🚫 PG中不可能发生 | ✅ 可能发生 | ✅ 可能发生 | ✅ 可能发生 |
| Read committed (PG默认) | 🚫 不可能发生 | ✅ 可能发生 | ✅ 可能发生 | ✅ 可能发生 |
| Repeatable read | 🚫 不可能发生 | 🚫 不可能发生 | 🚫 PG中不可能发生 (标准SQL中允许) | ✅ 可能发生 |
| Serializable | 🚫 不可能发生 | 🚫 不可能发生 | 🚫 不可能发生 | 🚫 不可能发生 |
注:序列化异常是指两个并发事务的结果与它们严格按照先后顺序执行的结果不一致,这只能通过最高的 Serializable 级别来解决。
开发建议
- 日常开发: 绝大多数应用使用 PostgreSQL 默认的
READ COMMITTED即可,性能最好。在代码层面(如 Java Spring)处理并发更新时,配合乐观锁(版本号机制)就能解决大部分问题。 - 需要强一致性视图的报表/对账: 如果你需要在一个事务中运行多个复杂的统计查询,且要求数据状态在整个事务期间绝对冻结,请开启
REPEATABLE READ。 - 极高财务级别的并发修改: 当涉及到多个并发事务基于彼此的结果进行复杂的计算和写入时,才考虑使用性能开销较大的
SERIALIZABLE。