事务是所有数据库系统的一个基本概念。事务的本质是将多个步骤捆绑成一个单一的、要么全部成功要么全部失败的操作。步骤之间的中间状态对其他并发事务不可见,如果发生某些导致事务无法完成的故障,则所有步骤都不会对数据库产生任何影响。
例如,考虑一个包含各种客户账户余额以及分行总存款余额的银行数据库。假设我们想要记录从 Alice 的账户向 Bob 的账户支付 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');
这些命令的细节在这里并不重要;重要的是,完成这个相当简单的操作涉及到几个单独的更新。我们银行的管理人员希望确保所有这些更新要么全部发生,要么都不发生。如果系统故障导致 Bob 收到未从 Alice 账户扣除的 100.00 美元,那肯定是不行的。如果 Alice 在未向 Bob 账户转账的情况下被扣款,她也绝不会成为一个满意的客户。我们需要保证,如果操作过程中出现问题,则迄今执行的所有步骤都不会生效。将更新分组到一个事务中可以保证这一点。事务被认为是原子性的:从其他事务的角度来看,它要么完全发生,要么根本不发生。
我们还希望保证,一旦事务完成并得到数据库系统的确认,它确实已被永久记录,即使随后发生崩溃也不会丢失。例如,如果我们正在记录 Bob 的现金提取,我们不希望出现任何机会导致在他走出银行大门后,从其账户中扣除的款项在崩溃中消失。事务性数据库保证事务所做的所有更新都在事务报告完成之前记录到永久存储(即磁盘)中。
事务性数据库的另一个重要特性与原子更新的概念密切相关:当多个事务并发运行时,每个事务都不应该能够看到其他事务所做的不完整更改。例如,如果一个事务正在忙于计算所有分行余额,那么它不应该包含 Alice 分行的借方但未包含 Bob 分行的贷方,反之亦然。因此,事务不仅在对数据库的永久影响方面必须是原子性的,而且在发生时可见性方面也必须是原子性的。打开的事务迄今所做的更新对其他事务不可见,直到事务完成,这时所有更新将同时变得可见。
在PostgreSQL 中,事务是通过用BEGIN
和 COMMIT
命令包围事务的 SQL 命令来设置的。因此,我们的银行事务实际上看起来像这样:
BEGIN; UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; -- etc etc COMMIT;
如果在事务过程中,我们决定不想提交(也许我们刚刚注意到 Alice 的余额变为负数),我们可以发出ROLLBACK
命令而不是COMMIT
命令,并且我们迄今为止的所有更新都将被取消。
PostgreSQL 实际上将每个 SQL 语句视为在事务中执行。如果您不发出BEGIN
命令,则每个单独的语句都有一个隐式的BEGIN
和(如果成功)COMMIT
包围它。由BEGIN
和COMMIT
包围的一组语句有时称为事务块。
某些客户端库会自动发出BEGIN
和COMMIT
命令,因此您可能会在不请求的情况下获得事务块的效果。请查看您正在使用的接口的文档。
可以通过使用保存点以更细粒度的方式控制事务中的语句。保存点允许您选择性地丢弃事务的一部分,同时提交其余部分。在使用SAVEPOINT
定义保存点后,如果需要,您可以使用ROLLBACK TO
回滚到该保存点。定义保存点和回滚到该保存点之间事务的所有数据库更改都将被丢弃,但早于保存点的更改将被保留。
回滚到保存点后,它将继续被定义,因此您可以多次回滚到它。相反,如果您确定不再需要回滚到某个特定的保存点,则可以将其释放,以便系统可以释放一些资源。请记住,释放或回滚到保存点将自动释放在其之后定义的所有保存点。
所有这些都发生在事务块内,因此其中任何内容对其他数据库会话都不可见。当且仅当您提交事务块时,已提交的操作作为单元对其他会话可见,而回滚的操作则永远不会可见。
回想一下银行数据库,假设我们从 Alice 的账户中扣除 100.00 美元,并将这笔钱存入 Bob 的账户,但后来发现我们应该将其存入 Wally 的账户。我们可以使用保存点来执行此操作,如下所示:
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
是在系统由于错误而使事务块进入中止状态后恢复对事务块控制的唯一方法,除非完全回滚并重新开始。
如果您在文档中发现任何不正确的内容,与您对特定功能的体验不符,或者需要进一步澄清,请使用此表单 报告文档问题。