事务是所有数据库系统的基本概念。事务的本质是将多个步骤捆绑到一个单一的、要么全部执行要么全部不执行的操作中。步骤之间的中间状态对其他并发事务是不可见的,如果发生某些故障阻止事务完成,则所有步骤都不会影响数据库。
例如,考虑一个银行数据库,其中包含各种客户账户的余额以及各分行的存款总余额。假设我们要记录一笔从 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 没有收到款项,那么 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
是唯一可以重新控制由于错误而被系统置于中止状态的事务块的方法,而无需将其完全回滚并重新开始。
如果您在文档中看到任何不正确、与您使用特定功能的体验不符或需要进一步澄清的内容,请使用此表单报告文档问题。