事务是所有数据库系统的一个基本概念。一次事务的要点就是把多个步骤捆绑成一个单一的、不成功则成仁的操作。
比如,假设一个银行的数据库包含各种客户帐户的余额,以及每个分行的总余额。假设我们要记录一次从 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 收到 100 块不是 Alice 支付的钱,也不希望 Alice 老是不花钱从 Bob 那里拿到物品。我们需要保证:如果在操作的过程中出了差错,那么所有这些步骤都不会发生效果。把这些更新组合成一个事务就给予我们这样的保证。事务被认为是原子的:从其它事务的角度来看,它要么是全部发生,要么完全不发生。
我们还需要保证:一旦一个事务完成并且得到数据库系统的认可,那么它必须被真正永久地存储,并且不会在随后的崩溃中消失。比如,如果我们记录到了一个 Bob 撤单的动作,那么我们不希望仅仅在他走出银行大门之后的一次崩溃就会导致对他的帐户的扣减动作消失。一个事务型数据库保证一个事务所做的所有更新在事务发出完成响应之前都记录到永久的存储中(也就是磁盘)。
在 PostgreSQL 里,一个事务是通过把 SQL 命令用 BEGIN 和 COMMIT 命令包围实现的。因此我们的银行事务实际上看起来像下面这样:
BEGIN; UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; -- 等等 COMMIT;
如果在该事务的过程中,我们决定不做提交(可能是我们刚发现 Alice 的余额是负数),那么我们可以发出 ROLLBACK 而不是 COMMIT 命令,那么到目前为止我们的所有更新都会被取消。
PostgreSQL 实际上把每个 SQL 语句当做在一个事务中执行来看待。如果你没有发出 BEGIN 命令,那么每个独立的语句都被一个隐含的 BEGIN 和(如果成功的话) COMMIT 包围。一组包围在 BEGIN 和 COMMIT 之间的语句有时候被称做 事务块。
【注意】一些客户库自动发出 BEGIN 和 COMMIT ,因此你可能不需要特意请求就可以获得事务块的效果。查看你使用的接口的文档。
我们可以通过使用保存点的方法,在一个事务里更加精细地控制其中的语句。保存点允许你选择性地抛弃事务中的某些部分,而提交剩下的部分。在用 SAVEPOINT 定义了一个保存点后,如果需要,你可以使用 ROLLBACK TO 回滚到该保存点。则该事务在定义保存点到 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'; -- 呀!加错钱了,应该用 Wally 的账号 ROLLBACK TO my_savepoint; UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Wally'; COMMIT;
这个例子当然是实在太简单了,但是通过使用保存点,我们可以对事务块有大量的控制。并且,ROLLBACK TO 是除了事务全部回滚,重新来过之外,唯一可以用于重新控制一个因错误而被系统置于退出状态事务的方法