事务
# 130.事务
在DBMS中,事务(transaction)是非常重要的概念
# 有事务之前
举个例子,上层决定把运动 T 恤的销售单价下调 1000 日元,同时把 T 恤衫的销售单价上浮 1000 日元,那么使用update即可。
①首先将运动T恤的销售单价降低 1000 日元
UPDATE Product
SET sale_price = sale_price - 1000
WHERE product_name = '运动T恤';
2
3
②再将T恤衫的销售单价上浮 1000 日元
UPDATE Product
SET sale_price = sale_price + 1000
WHERE product_name = 'T恤衫';
2
3
如果只执行了①的操作而忘记了执行②的操作,或者反过来只执行了②的操作而忘记了执行①的操作,那么更新就算失败的。
再来考虑一个例子:账户A给账户B赚钱100,先从账户A减去100,再去账户B增加100,如果有一个更新失败,那么就会造成财产的损失。
遇到这种需要执行一系列更新操作的情况,一定要使用事务来进行处理。
# 创建事务
基本语法:
事务开始语句;
DML语句①;
DML语句②;
DML语句③;
.
.
.
事务结束语句(COMMIT或者ROLLBACK);
2
3
4
5
6
7
8
9
使用事务开始语句和事务结束语句,将一系列 DML 语句(INSERT/UPDATE/DELETE 语句)括起来,就实现了一个事务处理。
这时需要特别注意的是事务的开始语句 。实际上,在标准 SQL 中并没有定义事务的开始语句,而是由各个 DBMS 自己来定义的。比较有代表性的语法如下所示。
与之相对,事务结束语句只有 COMMIT 和 ROLLBACK 两种,在所有的 RDBMS 中都是通用的。
--SQL Server、PostgreSQL
BEGIN TRANSACTION
--MySQL
START TRANSACTION
--Oracle、DB2
无
2
3
4
5
6
7
8
例如使用之前的那两个 UPDATE (①和②)创建出的事务:
--SQL Server PostgreSQL
BEGIN TRANSACTION;
-- 将运动T恤的销售单价降低1000日元
UPDATE Product
SET sale_price = sale_price - 1000
WHERE product_name = '运动T恤';
-- 将T恤衫的销售单价上浮1000日元
UPDATE Product
SET sale_price = sale_price + 1000
WHERE product_name = 'T恤衫';
COMMIT;
--MySQL
START TRANSACTION;
-- 将运动T恤的销售单价降低1000日元
UPDATE Product
SET sale_price = sale_price - 1000
WHERE product_name = '运动T恤';
-- 将T恤衫的销售单价上浮1000日元
UPDATE Product
SET sale_price = sale_price + 1000
WHERE product_name = 'T恤衫';
COMMIT;
--Oracle DB2
-- 将运动T恤的销售单价降低1000日元
UPDATE Product
SET sale_price = sale_price - 1000
WHERE product_name = '运动T恤';
-- 将T恤衫的销售单价上浮1000日元
UPDATE Product
SET sale_price = sale_price + 1000
WHERE product_name = 'T恤衫';
COMMIT;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
如上所示,各个 DBMS 事务的开始语句都不尽相同,其中 Oracle 和 DB2 并没有定义特定的开始语句。
反之,事务的结束需要用户明确地给出指示。结束事务的指令有commit 和 rollback
# COMMIT:提交处理
COMMIT 是提交事务包含的全部更新处理的结束指令。一旦提交 ,就无法恢复到事务开始前的状态了。因此,在提交之前一定要确认是否真的需要进行这些更新。
注意:一旦提交,就很难恢复到之前的数据了,就好比delete一样,删除后就很难恢复了。
# ROLLBACK:取消处理
ROLLBACK 是取消事务包含的全部更新处理的结束指令。一旦回滚 ,数据库就会恢复到事务开始之前的状态。通常回滚并不会像提交那样造成大规模的数据损失。
代码清单 4-22 事务回滚的例子
--SQL Server PostgreSQL
BEGIN TRANSACTION; ------------------- ①
-- 将运动T恤的销售单价降低1000日元
UPDATE Product
SET sale_price = sale_price - 1000
WHERE product_name = '运动T恤';
-- 将T恤衫的销售单价上浮1000日元
UPDATE Product
SET sale_price = sale_price + 1000
WHERE product_name = 'T恤衫';
ROLLBACK;
2
3
4
5
6
7
8
9
10
11
12
13
14
上述事务处理执行之后,表中的数据不会发生任何改变。这是因为执行最后一行的 ROLLBACK 之后,所有的处理都被取消了。因此,回滚执行起来就无需像提交时那样小心翼翼了(即使是想要提交的情况,也只需要重新执行事务处理就可以了)。
注意:不能回退select子句(回退了也没意思),也不能回退create和drop操作,事务中可以使用这些语句,但进行回退时,这些操作也不撤销
# 事务处理何时开始
之前我们说过,事务并没有标准的开始指令存在,而是根据 DBMS 的不同而不同。
实际上,几乎所有的数据库产品的事务都无需开始指令。这是因为大部分情况下,事务在数据库连接建立时就已经悄悄开始了,并不需要用户再明确发出开始指令。例如,使用 Oracle 时,数据库连接建立之后,第一条 SQL 语句执行的同时,事务就已经悄悄开始了。
像这样不使用指令而悄悄开始事务的情况下,应该如何区分各个事务呢?通常会有如下两种情况。
- 每条 SQL 语句就是一个事务(自动提交模式,auto commit)
- 直到用户执行 COMMIT 或者 ROLLBACK 为止算作一个事务
通常的 DBMS 都可以选择其中任意一种模式。默认使用自动提交模式的 DBMS 有 SQL Server 、PostgreSQL 和 MySQL等。 DML 语句如下所示,每一条语句都括在事务的开始语句和结束语句之中。
BEGIN TRANSACTION;
-- 将运动T恤的销售单价降低1000日元
UPDATE Product
SET sale_price = sale_price - 1000
WHERE product_name = '运动T恤';
COMMIT;
BEGIN TRANSACTION;
-- 将T恤衫的销售单价上浮1000日元
UPDATE Product
SET sale_price = sale_price + 1000
WHERE product_name = 'T恤衫';
COMMIT;
2
3
4
5
6
7
8
9
10
11
12
13
例如,PostgreSQL 的用户手册中有如下记述:“PostgreSQL 中所有的 SQL 指令语句都在事务内执行。即使不执行 BEGIN ,这些命令语句也会在执行时悄悄被括在 BEGIN 和 COMMIT (如果成功的话)之间。”(《PostgreSQL 9.5.2 文档》“3-4 节事务”)
在默认使用 B 模式的 Oracle 中,事务都是直到用户自己执行提交或者回滚指令才会结束。
自动提交的情况需要特别注意的是 DELETE 语句。如果不是自动提交,即使使用 DELETE 语句删除了数据表,也可以通过 ROLLBACK 命令取消该事务的处理,恢复表中的数据。但这仅限于明示开始事务,或者关闭自动提交的情况。如果不小心在自动提交模式下执行了 DELETE 操作,即使再回滚也无济于事了。这是一个很严重的问题,初学者难免会碰到这样的麻烦。
# ACID 特性
DBMS 的事务都遵循四种特性,将这四种特性的首字母结合起来统称为 ACID 特性 。这是所有 DBMS 都必须遵守的规则。
# 原子性(Atomicity)
原子性是指在事务结束时,其中所包含的更新处理要么全部执行,要么完全不执行,也就是要么占有一切要么一无所有。例如,在之前的例子中,在事务结束时,绝对不可能出现运动 T 恤的价格下降了,而 T 恤衫的价格却没有上涨的情况。该事务的结束状态,要么是两者都执行了(COMMIT ),要么是两者都未执行(ROLLBACK )。
从事务中途停止的角度去考虑,就能比较容易理解原子性的重要性了。由于用户在一个事务中定义了两条 UPDATE 语句,DBMS 肯定不会只执行其中一条,否则就会对业务处理造成影响。
# 一致性(Consistency)
一致性指的是事务中包含的处理要满足数据库提前设置的约束,如主键约束或者 NOT NULL 约束等。例如,设置了 NOT NULL 约束的列是不能更新为 NULL 的,试图插入违反主键约束的记录就会出错,无法执行。对事务来说,这些不合法的 SQL 会被回滚。也就是说,这些 SQL 处理会被取消,不会执行。
一致性也称为完整性 。
# 隔离性(Isolation)
隔离性指的是保证不同事务之间互不干扰的特性。该特性保证了事务之间不会互相嵌套。此外,在某个事务中进行的更改,在该事务结束之前,对其他事务而言是不可见的。因此,即使某个事务向表中添加了记录,在没有提交之前,其他事务也是看不到新添加的记录的。
# 持久性(Durability)
持久性也可以称为耐久性,指的是在事务(不论是提交还是回滚)结束后,DBMS 能够保证该时间点的数据状态会被保存的特性。即使由于系统故障导致数据丢失,数据库也一定能通过某种手段进行恢复。
如果不能保证持久性,即使是正常提交结束的事务,一旦发生了系统故障,也会导致数据丢失,一切都需要从头再来。
保证持久性的方法根据实现的不同而不同,其中最常见的就是将事务的执行记录保存到硬盘等存储介质中(该执行记录称为日志 )。当发生故障时,可以通过日志恢复到故障发生前的状态。