# 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美元,而这并没有从Alice借出,那么它肯定不会发生。如果她没有被银行贷记,爱丽丝也不会一直是一个快乐的顾客。我们需要一个保证,如果某个错误的部分通过操作,到目前为止执行的步骤都不会生效。将更新分组为交易给我们这个保证。据说交易是原子的:从其他事务的角度来看,它要么完全发生,要么根本不发生。
我们还希望保证,一旦一个事务完成并得到数据库系统的承认,它确实已经永久记录,即使在之后不久发生崩溃,也不会丢失。例如,如果我们正在记录鲍勃的现金提款,我们不希望任何机会,在他走出银行门后,他的账户的借方会在崩溃中消失。事务数据库保证事务所做的所有更新都在报告事务完成之前登录到永久存储(即磁盘上)。
事务数据库的另一个重要属性与原子更新的概念密切相关:当多个事务同时运行时,每个事务都不应该看到其他事务所做的不完整的更改。例如,如果一个交易忙于总计所有分支余额,则不包括Alice分行的借方,而不是将贷记计入Bob分行,反之亦然。因此,事务必须是全部或根本,不仅是因为它们对数据库的永久影响,而且还必须考虑到它们发生时的可见性。在事务完成之前,打开事务所做的更新对其他事务都是不可见的,因此所有更新都会同时可见。
在PostgreSQL中,通过围绕事务的SQL命令来设置事务开始
和犯罪
命令。所以我们的银行交易实际上看起来像:
BEGIN;
UPDATE accounts SET balance = balance - 100.00
WHERE name = 'Alice';
-- etc etc
COMMIT;
如果在事务进行到一半时,我们决定不想提交(也许我们只是注意到Alice的余额为负数),我们可以发出命令回降
而不是犯罪
,我们目前的所有更新都将被取消。
PostgreSQL实际上将每个SQL语句都视为在事务中执行。如果您不发布开始
命令,则每个语句都有一个隐式开始
以及(如果成功)犯罪
围绕着它。一组由开始
和犯罪
有时被称为事务块.
# 笔记
一些客户端库存在问题开始
和犯罪
命令自动执行,这样您就可以在不询问的情况下获得事务块的效果。检查您正在使用的界面的文档。
通过使用存储点.保存点允许您有选择地放弃部分事务,同时提交其余事务。在用保存点
,如果需要,您可以使用回滚到
。在定义保存点和回滚到保存点之间,事务的所有数据库更改都将被放弃,但保留早于保存点的更改。
回滚到保存点后,它将继续被定义,因此您可以多次回滚到该保存点。相反,如果确定不需要再次回滚到某个特定的保存点,则可以释放该保存点,这样系统就可以释放一些资源。请记住,释放或回滚到某个保存点都会自动释放该保存点之后定义的所有保存点。
所有这些都是在事务块中发生的,因此其他数据库会话都看不到。当您提交事务块时,提交的操作作为一个单元对其他会话可见,而回滚的操作根本不可见。
记住银行数据库,假设我们从爱丽丝的账户中借记100美元,贷记鲍勃的账户,但后来发现我们应该贷记沃利的账户。我们可以使用这样的保存点:
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;
当然,这个例子过于简单,但通过使用保存点,事务块中有很多控制是可能的。此外回滚到
是恢复对由于错误而被系统置于中止状态的事务块的控制的唯一方法,而不是将其完全回滚并重新启动。