标准 SQL 定义了四个递进等级的事务隔离级别。其中最严格的是可串行化(Serializable)。标准规定:一组并发执行的可串行化事务,其最终产生的数据结果必须与按照某种顺序将这些事务逐一单独串行执行的效果完全等价。其余三个隔离级别则是通过定义并发事务交互时严禁出现的各类异常现象来界定的。
各类隔离级别所针对预防的核心异象包括:
SQL 标准与 PostgreSQL 实现的隔离级别对照见 Table 13.1。
Table 13.1. 事务隔离级别异象对照
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 串行化异常 |
|---|---|---|---|---|
| 读未提交(Read uncommitted) | 标准允许,但 PG 内部杜绝 | 可能发生 | 可能发生 | 可能发生 |
| 读已提交(Read committed) | 不会发生 | 可能发生 | 可能发生 | 可能发生 |
| 可重复读(Repeatable read) | 不会发生 | 不会发生 | 标准允许,但 PG 内部杜绝 | 可能发生 |
| 可串行化(Serializable) | 不会发生 | 不会发生 | 不会发生 | 不会发生 |
在 PostgreSQL 中,你可以请求声明以上四种标准隔离级别中的任意一种,但在系统内部实际上仅实现了三种物理隔离级别:即读未提交模式的行为与读已提交完全一致。因为在 MVCC 架构下,允许脏读本身毫无意义。同样,PostgreSQL 的可重复读实现自动杜绝了幻读异象。
要设置当前事务的隔离级别,使用 SET TRANSACTION 命令。
读已提交(Read Committed)是 PostgreSQL 的默认隔离级别。在该级别下,普通的 SELECT 查询仅能看到在当前语句开始执行瞬间之前已成功提交的数据,绝不会窥见未提交的中间脏数据,也不会看到其他并发事务在当前单条语句执行期间刚提交的变更。在单次查询内部,数据视图是绝对静态快照。然而,在同一个事务内部连续发起的两条前后相邻的 SELECT 语句可能会看到不同的数据,因为如果在第一条语句结束之后、第二条语句开始之前有其他事务提交了变更,第二条语句会建立全新的快照并感知到这一提交。
当执行 UPDATE、DELETE 或 SELECT FOR UPDATE 时,若目标行正在被另一个并发事务修改,当前命令将处于等待状态,直到先行事务提交或回滚。若先行事务回滚,当前命令继续处理该行;若先行事务提交修改,当前命令会在该行的最新版本上重新评估 WHERE 条件,条件若依然满足,则对最新行版本继续执行操作。
可重复读(Repeatable Read)隔离级别仅能看到在整个事务首条查询或数据修改命令开始瞬间之前已提交的数据。在整个事务生命周期内,所有查询均锁定在同一个固定的只读全局快照上,无论其他并发事务如何提交,都不会影响当前事务内部后续查询的返回内容。
当可重复读事务试图更新或锁定已被另一个并发事务修改并提交的同一数据行时,系统会直接抛出序列化失败错误中断当前事务:
ERROR: could not serialize access due to concurrent update
应用层必须做好对该可重试错误(SQLSTATE 40001)的自动重试准备。
可串行化(Serializable)提供最严格的最高事务隔离保证。该级别在可重复读快照隔离(SSI)的基础上,在底层引入了非阻塞的谓词锁(SIREAD 锁)依赖图分析。系统在后台实时监控所有并发事务的读写依赖关系,一旦检测到可能引发环状因果依赖(写偏差等串行化反常异象)的危险模式,系统会主动回滚其中一个事务以打破死锁环,确保所有成功提交事务的等价串行性。