pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
SQL标准根据并发事务之间必须防止的三个现象,定义了四个事务隔离级别。这些不受欢迎的现象是:
四种事务隔离级别及其对应行为见 表 9.1。
表 9.1. SQL 事务隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 |
| 读已提交 | 不可能 | 可能 | 可能 |
| 可重复读 | 不可能 | 不可能 | 可能 |
| 可串行化 | 不可能 | 不可能 | 不可能 |
PostgreSQL 提供读已提交和可串行化两个隔离级别。
读已提交 是 PostgreSQL 中的默认隔离级别。当事务使用这一隔离级别时, SELECT 查询只能看到查询开始之前已经提交的数据;它既看不到未提交的数据,也看不到查询执行期间并发事务提交的更改。(不过, SELECT 能看到其自身事务中先前执行的更新效果,即使这些更新尚未提交。)实际上, SELECT 查询看到的是该查询开始运行瞬间的数据库快照。注意,即使两个连续的 SELECT 命令位于同一个事务中,如果其他事务在第一个 SELECT 执行期间提交了更改,这两个命令也可能看到不同的数据。
UPDATE、DELETE 和 SELECT FOR UPDATE 命令在搜索目标行方面的行为与 SELECT 相同:它们只会找到查询开始时已提交的目标行。然而,这样的目标行在被找到时,可能已经被另一个并发事务更新(或删除或标记为更新)。在这种情况下,想要更新的事务会等待第一个更新事务提交或回滚(如果它仍在进行中)。如果第一个更新者回滚,其效果即被作废,第二个更新者可以继续更新最初找到的行。如果第一个更新者提交,第二个更新者在该行已被删除时会忽略该行,否则会尝试把它的操作应用到该行更新后的版本上。查询的搜索条件(WHERE 子句)会被重新求值,以查看行更新后的版本是否仍然满足搜索条件。若满足,第二个更新者就从行的更新版本开始继续执行其操作。
由于上述规则,更新型查询可能看到不一致的快照——它们能看到影响自己正要更新的那些行的并发更新查询的效果,却看不到那些查询对数据库中其他行的影响。这种行为使得读已提交模式不适合涉及复杂搜索条件的查询。不过,对较简单的场景它正合适。例如,考虑用如下事务更新银行账户余额:
BEGIN; UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 12345; UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 7534; COMMIT;
如果两个这样的事务并发地尝试更改账户 12345 的余额,我们显然希望第二个事务从该账户行更新后的版本开始。因为每个查询只影响一个预先确定的行,让它看到行的更新版本不会造成任何麻烦的不一致。
由于在读已提交模式下,每个新查询都从一个新快照开始,而该快照包含截至那一刻已提交的所有事务,同一事务中后续的查询无论如何都会看到已提交的并发事务的效果。这里的问题在于:在单条查询之内,我们看到的数据库视图是否绝对一致。
读已提交模式所提供的部分事务隔离对于许多应用来说已经足够,而且这种模式既快速又容易使用。 不过,它并不能满足所有场景。执行复杂查询和更新的应用,可能需要比读已提交模式所提供的更严格一致的数据库视图。
可串行化提供最严格的事务隔离。该级别模拟串行的事务执行,就好像事务是一个接一个串行执行的,而不是并发执行的。不过,使用这一级别的应用必须准备好在出现串行化失败时重试事务。
当事务使用可串行化级别时, SELECT 查询只能看到事务开始之前已经提交的数据;它既看不到未提交的数据,也看不到事务执行期间并发事务提交的更改。(不过, SELECT 能看到其自身事务中先前执行的更新效果,即使这些更新尚未提交。)这与读已提交的不同之处在于, SELECT 看到的是事务开始时刻的快照,而不是事务内当前查询开始时刻的快照。因此,同一事务内先后执行的 SELECT 总是看到相同的数据。
UPDATE、DELETE和SELECT FOR UPDATE命令在搜索目标行方面的行为与SELECT相同:它们只会找到事务开始时刻已提交的目标行。然而,这样的目标行在被找到时,可能已经被另一个并发事务更新(或删除或标记为更新)。在这种情况下,可串行化事务将等待第一个更新事务提交或回滚(如果仍在进行)。如果第一个更新事务回滚,那么它的作用将被撤销,可串行化事务就可以继续更新最初找到的行。但如果第一个更新事务提交了(并且确实更新或删除了该行,而不仅仅是选取它准备更新),那么可串行化事务将被回滚,并收到以下消息:
ERROR: Can't serialize access due to concurrent update
因为可串行化事务不能修改在它开始之后被其他事务更改过的行。
当应用收到这条错误消息时,应当中止当前事务,并从头重试整个事务。 第二次执行时,该事务会把先前已提交的更改视为其初始数据库视图的一部分, 因此以该行的新版本作为新事务更新的起点时,就不存在逻辑冲突。
注意,只有更新型事务才可能需要重试——只读事务永远不会发生串行化冲突。
可串行化模式严格保证每个事务都看到完全一致的数据库视图。不过,当并发更新使得无法维持串行执行的假象时,应用必须准备好重试事务。由于重做复杂事务的代价可能很高,只有当更新型事务包含的逻辑复杂到在读已提交模式下可能给出错误结果时,才推荐使用这一模式。最常见的情况是:事务执行多条必须看到相同数据库视图的连续查询时,需要使用可串行化模式。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。