↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1
历史版本PostgreSQL 7.4 已于 2010 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本。

12.2. 事务隔离 #

SQL标准根据并发事务之间必须防止的三个现象,定义了四个事务隔离级别。这些不受欢迎的现象是:

dirty read

一个事务读取了由另一个并发未提交事务写入的数据。

nonrepeatable read

一个事务重新读取它之前读过的数据,发现这些数据已经被另一个事务修改(且该事务已在初次读取之后提交)。

phantom read

一个事务重新执行一个返回满足某个搜索条件的行集合的查询,发现满足该条件的行集合由于另一个最近提交的事务而发生了变化。

四种事务隔离级别及其对应行为见 表 12.1。

表 12.1. SQL 事务隔离级别

隔离级别 脏读 不可重复读 幻读
读未提交 可能 可能 可能
读已提交 不可能 可能 可能
可重复读 不可能 不可能 可能
可串行化 不可能 不可能 不可能

PostgreSQL 提供读已提交和可串行化两个隔离级别。

12.2.1. 读已提交隔离级别 #

读已提交是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 的余额,我们显然希望第二个事务从该账户行的已更新版本开始。 因为每个命令只影响一个预先确定的行,让它看到该行的已更新版本不会造成任何麻烦的不一致。

因为在读已提交模式中,每个新命令都从一个新快照开始,而该快照包含截至当时已提交的所有事务, 所以同一事务中的后续命令无论如何都会看到并发事务已提交的效果。这里讨论的问题在于, 在单个命令内部,我们是否能看到数据库的一个绝对一致视图。

读已提交模式所提供的部分事务隔离对于许多应用来说已经足够,而且这种模式既快速又容易使用。 不过,它并不能满足所有场景。执行复杂查询和更新的应用,可能需要比读已提交模式所提供的更严格一致的数据库视图。

12.2.2. 可串行化隔离级别 #

可串行化隔离级别提供最严格的事务隔离。该级别模拟串行事务执行,就像事务是一个接一个串行执行而不是并发执行一样。不过,使用该级别的应用必须准备好在发生串行化失败时重试事务。

当事务使用可串行化级别时,SELECT查询只能看到事务开始之前已经提交的数据;它既看不到未提交的数据,也看不到事务执行期间并发事务提交的更改。(不过,SELECT能看到其自身事务中先前执行的更新效果,即使这些更新尚未提交。)这一点与读已提交不同:SELECT看到的是事务开始时刻的快照,而不是事务中当前查询开始时刻的快照。因此,单个事务中连续的SELECT命令看到相同的数据,也就是说,它们看不到在本事务启动之后由其他事务提交的更改。(这种行为对于报表类应用来说可能非常理想。)

UPDATE、DELETE和SELECT FOR UPDATE命令在搜索目标行方面的行为与SELECT相同:它们只会找到事务开始时刻已提交的目标行。然而,这样的目标行在被找到时,可能已经被另一个并发事务更新(或删除或标记为更新)。在这种情况下,可串行化事务将等待第一个更新事务提交或回滚(如果仍在进行)。如果第一个更新事务回滚,那么它的作用将被撤销,可串行化事务就可以继续更新最初找到的行。但如果第一个更新事务提交了(并且确实更新或删除了该行,而不仅仅是选取它准备更新),那么可串行化事务将被回滚,并收到以下消息:

ERROR:  could not serialize access due to concurrent update

这是因为可串行化事务不能修改或锁定在它开始之后被其他事务更改过的行。

当应用收到这条错误消息时,应当中止当前事务,并从头重试整个事务。 第二次执行时,该事务会把先前已提交的更改视为其初始数据库视图的一部分, 因此以该行的新版本作为新事务更新的起点时,就不存在逻辑冲突。

注意,只有更新事务才可能需要重试;只读事务永远不会发生串行化冲突。

可串行化模式严格保证每个事务看到的都是完全一致的数据库视图。然而,当并发更新使得串行执行的假象无法维持时,应用必须准备好重试事务。由于重做复杂事务的代价可能相当可观,只有当更新事务包含的逻辑复杂到在读已提交模式下可能给出错误结果时,才推荐使用可串行化模式。最常见的情况是,当一个事务要执行多条必须看到相同数据库视图的连续命令时,就需要使用可串行化模式。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。