2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 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

3.4. 事务 #

事务是所有数据库系统的基本概念。事务的核心思想是将多个步骤捆绑成一个单一的、要么全做要么全不做的操作。在步骤之间的中间状态对其他并发事务是不可见的,如果发生任何导致事务无法完成的故障,那么所有步骤都不会对数据库产生任何影响。

例如,考虑一个银行数据库,其中包含各种客户账户的余额以及分行的总存款余额。假设我们要记录从爱丽丝账户到鲍勃账户的 100.00 美元的付款。极度简化地说,SQL 命令可能如下所示:

UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
UPDATE branches SET balance = balance - 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Alice');
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
UPDATE branches SET balance = balance + 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Bob');

这些命令的细节在此处并不重要;重要的是,要完成这个相当简单的操作需要涉及多个独立的更新。我们的银行官员希望得到保证,要么所有这些更新都发生,要么都不发生。系统故障导致鲍勃收到 100.00 美元,而爱丽丝的账户未被扣款,这绝对是不可接受的。爱丽丝也不会长期保持满意,如果她的账户被扣款而鲍勃的账户未被记入。我们需要保证,如果在操作过程中发生任何问题,那么到目前为止已执行的任何步骤都不会生效。将这些更新分组到一个 事务 中可以提供此保证。一个事务被称为 原子:从其他事务的角度来看,它要么完全发生,要么根本不发生。

我们还希望得到保证,一旦事务完成并被数据库系统确认,它就已经被永久记录,即使之后不久发生崩溃也不会丢失。例如,如果我们正在记录鲍勃的现金提取,我们不希望有任何机会在他离开银行后,他账户上的借记会因为崩溃而消失。事务性数据库保证,在事务报告完成之前,由事务所做的所有更新都已记录在永久存储(即磁盘)中。

事务数据库的另一个重要特性与原子更新的概念密切相关:当多个事务并发运行时,每个事务都不应该能够看到其他事务尚未完成的更改。例如,如果一个事务正在忙于计算所有分行的总余额,那么它在看到爱丽丝分行的借记但没有看到鲍勃分行的贷记,或者反之亦然,这是不可接受的。因此,事务不仅在其对数据库的永久影响方面必须是“全有或全无”的,而且在其发生时的可见性方面也必须如此。未完成事务所做的更改在事务完成之前对其他事务是不可见的,一旦事务完成,所有更改将同时变得可见。

PostgreSQL 中,事务是通过将事务的 SQL 命令用 BEGINCOMMIT 命令包围来设置的。所以我们的银行交易实际上看起来是这样的:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
-- etc etc
COMMIT;

如果在事务进行过程中,我们决定不提交(也许我们刚刚注意到爱丽丝的余额变为负数),我们可以发出 ROLLBACK 命令而不是 COMMIT,并且到目前为止我们所有的更新都将被取消。

PostgreSQL 实际上将每个 SQL 语句都视为在事务中执行。如果您不发出 BEGIN 命令,那么每个单独的语句都会隐式地在其周围包装一个 BEGIN 和(如果成功)一个 COMMIT。一组被 BEGINCOMMIT 包围的语句有时被称为 事务块

注意

一些客户端库会自动发出 BEGINCOMMIT 命令,因此您可能在不明确要求的情况下获得事务块的效果。请查阅您正在使用的接口的文档。

通过使用 保存点,可以更精细地控制事务中的语句。保存点允许您选择性地放弃事务的某些部分,同时提交其余部分。在用 SAVEPOINT 定义了保存点之后,您可以根据需要用 ROLLBACK TO 回滚到该保存点。在定义保存点和回滚到该保存点之间的事务数据库更改将被丢弃,但早于保存点的更改将被保留。

回滚到保存点后,该保存点仍然被定义,因此您可以多次回滚到它。相反,如果您确定不再需要回滚到某个特定的保存点,则可以释放它,以便系统可以释放一些资源。请记住,释放保存点或回滚到保存点都会自动释放其后定义的所有保存点。

所有这些都发生在事务块内,因此它们对其他数据库会话都不可见。当且仅当您提交事务块时,提交的操作作为一个整体对其他会话可见,而被回滚的操作则完全不可见。

回想一下银行数据库,假设我们从爱丽丝的账户中扣除了 100.00 美元,并将 100.00 美元记入鲍勃的账户,后来才发现我们应该记入瓦利的账户。我们可以使用保存点这样做:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
-- oops ... forget that and use Wally's account
ROLLBACK TO my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Wally';
COMMIT;

当然,这个例子过于简化了,但是通过使用保存点,可以在事务块中实现很多控制。此外,除非完全回滚并重新开始,否则 ROLLBACK TO 是恢复因系统错误而进入中止状态的事务块的唯一方法。

提交更正

如果您在文档中发现任何不正确、与您对特定功能的体验不符或需要进一步澄清的内容,请使用 此表单 报告文档问题。