事务(Transaction)是数据库区别于文件系统的特性之一。
事务会把数据库从一种一致状态转变为另一种一致状态。这就是事务的任务。
Oracle中的事务体现了所有必要的ACID特征。ACID是以下4个词的缩写。
- 原子性(atomicity):事务中的所有动作要么都发生,要么都不发生。
- 一致性(consistency):事务将数据库从一种一致状态转变为下一种一致状态。
- 隔离性(isolation):一个事务的影响在该事务提交前对其他事务都不可见。
- 持久性(durability):事务一旦提交,其结果就是永久性的。
1.事务控制语句
Oracle事务会在修改数据的第一条语句处隐式开始(也就得到TX的第一条语句)。也可以使用set transaction 或DBMS_TRANSACTION包来显式地开始一个事务。如果发出commit 或者rollback语句,就会显式地结束一个事务。
Note:不是所有的Rollback语句都能结束事务,请注意ROLLBACK TO SAVEPOINT 命令不会结束事务!正确地写为ROLLBACK才能结束事务。
- COMMIT :想要使用这个语句最简形式,只需发出COMMIT.
- ROLLBACK:要想使用这个语句的最简形式,只需发出ROLLBACK。
- SAVEPOINT:SAVEPOINT允许你在事务中创建一个标记点(marked point)。一个事务中可以有多个SAVEPOINT
- ROLLBACK TO <SAVEPOINT>: 可以把事务回滚到标记点,而不回滚在此标记点之前的任何工作。
- SET TRANSACTION :允许你设置不同的事务属性,如事务的隔离级别以及事务是只读的还是可读写的。
2.原子性
2.1语句级原子性
scott@orclpdb1:orclcdb>
scott@orclpdb1:orclcdb> create table t2(cnt int);
create table t2(cnt int)
*
ERROR at line 1:
ORA-00955: name is already used by an existing object
scott@orclpdb1:orclcdb> drop table t2;
Table dropped.
scott@orclpdb1:orclcdb> create table t2(cnt int);
Table created.
scott@orclpdb1:orclcdb> insert into t2 values (0);
1 row created.
scott@orclpdb1:orclcdb> commit;
Commit complete.
scott@orclpdb1:orclcdb> create table t (x int check (x>0));
Table created.
scott@orclpdb1:orclcdb>
scott@orclpdb1:orclcdb> create trigger t_trigger
2 before insert or delete on t for each row
3 begin
4 if (inserting) then
5 update t2 set cnt = cnt + 1;
6 else
7 update t2 set cnt = cnt - 1;
8 end if;
9 dbms_output.put_line('I fired and updated ' || sql%rowcount || ' rows');
10 end;
11 /
Trigger created.
scott@orclpdb1:orclcdb> set serveroutput on
scott@orclpdb1:orclcdb> insert into t values(1);
I fired and updated 1 rows
1 row created.
scott@orclpdb1:orclcdb> insert into t values(-1);
I fired and updated 1 rows
insert into t values(-1)
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.SYS_C009274) violated
scott@orclpdb1:orclcdb> select * from t2;
CNT
----------
1
1 row selected.
scott@orclpdb1:orclcdb>
过程级原子性
Oracle把PL/SQL匿名块也当做是语句。
scott@orclpdb1:orclcdb>
scott@orclpdb1:orclcdb> create or replace procedure p
2 as
3 begin
4 insert into t values (1);
5 insert into t values (-1);
6 end;
7 /
Procedure created.
scott@orclpdb1:orclcdb> delete from t;
I fired and updated 1 rows
1 row deleted.
scott@orclpdb1:orclcdb> update t2 set cnt = 0;
1 row updated.
scott@orclpdb1:orclcdb> commit;
Commit complete.
scott@orclpdb1:orclcdb> select * from t;
no rows selected
scott@orclpdb1:orclcdb> select * from t2;
CNT
----------
0
1 row selected.
scott@orclpdb1:orclcdb>
以上创建了一个过程,这个过程不会成功。第二个INSERT 总会失败。
scott@orclpdb1:orclcdb>
scott@orclpdb1:orclcdb> begin
2 p;
3 end;
4 /
I fired and updated 1 rows
I fired and updated 1 rows
begin
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.SYS_C009274) violated
ORA-06512: at "SCOTT.P", line 5
ORA-06512: at line 2
scott@orclpdb1:orclcdb> select * from t;
no rows selected
scott@orclpdb1:orclcdb> select * from t2;
CNT
----------
0
1 row selected.
scott@orclpdb1:orclcdb>
Oracle把这个存储过程调用处理为一个原子语句。客户提交了一个代码块
scott@orclpdb1:orclcdb> begin
2 p;
3 end;
4 /
Oracle在它外面包了一个SAVEPOINT。由于P失败了,Oracle将数据库恢复到调用这个存储过程之前的时间点。
语句级原子性——靠的是PL/SQL例程本身不完成任何提交或回滚。COMMIT和ROLLBACK一般情况不应在PL/SQL中应用,只有PL/SQL存储过程的调用者才知道事务何时完成。在你开发的PL/SQL过程中,执行COMMIT和ROLLBACK是一个不好的编程实践。
如果提交一个稍微不同的代码块,会得到完全不同的结果。
scott@orclpdb1:orclcdb> begin
2 p;
3 exception
4 when others then
5 dbms_output.put_line('Error|||| ' || sqlerrm );
6 end;
7 /
I fired and updated 1 rows
I fired and updated 1 rows
Error|||| ORA-02290: check constraint (SCOTT.SYS_C009274) violated
PL/SQL procedure successfully completed.
scott@orclpdb1:orclcdb> select * from t;
X
----------
1
1 row selected.
scott@orclpdb1:orclcdb> select * from t2;
CNT
----------
1
1 row selected.
scott@orclpdb1:orclcdb>
以上两个代码的差别很微妙,一个代码块中包含WHEN OTHER 异常处理代码而另一个没有,但是你在应用中必须考虑到这些问题。向一个PL/SQL块增加异常处理器可能会显著地改变它的行为。对此可以用另一种方式编写代码,将语句级原子性恢复到整个PL/SQL块级原子性。如下所示:
scott@orclpdb1:orclcdb> delete from t2;
1 row deleted.
scott@orclpdb1:orclcdb> delete from t;
I fired and updated 0 rows
1 row deleted.
scott@orclpdb1:orclcdb> begin
2 savepoint sp;
3 p;
4 exception
5 when others then
6 rollback to sp;
7 dbms_output.put_line('Error|||| ' || sqlerrm );
8 end;
9 /
I fired and updated 0 rows
I fired and updated 0 rows
Error|||| ORA-02290: check constraint (SCOTT.SYS_C009274) violated
PL/SQL procedure successfully completed.
scott@orclpdb1:orclcdb> select * from t;
no rows selected
scott@orclpdb1:orclcdb> select * from t2;
no rows selected
scott@orclpdb1:orclcdb>
在此模仿了ORACLE通常用SAVEPOINT所做的工作,这样一来,我们不仅仍然能捕获和“忽略”错误,还能恢复原来的行为。这个例子只做说明用,其实是一种非常糟糕的编码实践。
事务级原子性
事务的总目标是把数据库从一种一致状态转变为另一种一致状态。
为了实现这个目标,事务也是原子性的,事务完成的所有工作要么完全提交并成为永久性的,要么会回滚并撤销。
DDL与原子性
数据定义语言(DDL)可以完成如下工作:
- (1)提交所有未完成的工作,结束当前已有的所有事务。
- (2)完成DDL操作,如CREATE TABLE
- (3) 如果DDL操作成功则提交,否则回滚DDL操作。
只要发出CREATE 、ALTER等DDL语句,就可以将现有的事务立即提交,并完成后面的DDL命令,这些DDL命令可能提交从而得到持久性结果,也可能因出现错误而回滚。
持久性
一个事务提交时,它的改变就是永久性的。即使数据库在提交之前完成之后随即崩溃,你也完全可以相信这些改变确实已经永久存储在数据库中。
- 使用COMMIT语句新增的WRITE扩展
- 在非分布式(只访问一个数据库,而不是多个数据库链接)PL/SQL代码块中执行COMMIT。
完整性约束和事务
完整性约束会在整个SQL语句得到处理之后才进行检查。也有一些可延迟的约束允许将完整性约束的验证延迟到应用请求时(发出一个SET CONSTRAINTS ALL IMMEDIATE)才完成,或者延迟到发出COMMIT时再检查。
IMMEDIATE 约束
为什么约束要在SQL语句执行之后才验证呢?
因为一条语句可能会使表的各行暂时“不一致”。
DEFERRABLE约束和级联更新
分布式事务
分布式事务的关键是数据库链接(database link )
数据库链接是一个数据库对象。
自治事务
自治事务允许你创建一个“事务中的事务”,它能独立于其父事务提交或回滚。
自治事务提供了一种用PL/SQL控制事务的新方法。可以用于
- 顶层匿名块。
- 本地(过程中的过程)、独立或打包的函数和过程。
- 对象类型的方法。
- 数据库触发器。
自治事务如何工作
创建一个简单的表来保存消息:
scott@orclpdb1:orclcdb> create table t(msg varchar2(25));
Table created.
接下来创建两个过程,每个过程只是将其名字插入到消息表中,然后提交。不过,其中一个过程是正常的过程,另一个编写为自治事务。我们将使用这些对象来显示在各种情况下哪些工作会在数据库中持久保留(被提交)。
首先是AUTONOMOUS_INSERT过程:
scott@orclpdb1:orclcdb> create or replace procedure Autonomous_Insert
2 as
3 pragma autonomous_transaction;
4 begin
5 insert into t values('Autonomous Insert');
6 commit;
7 end;
8 /
Procedure created.
注意 这里使用了pragma autonomous_transaction; 这个命令告诉数据库:执行这个过程时要作为一个新的自治事务来执行,而且独立于其父事务。
以下是“正常”的NONAUTONOMOUS_INSERT过程:
cott@orclpdb1:orclcdb> create or replace procedure NonAutonomous_Insert
2 as
3 begin
4 insert into t values('NonAutonomous Insert');
5 commit;
6 end;
7 /
Procedure created.
下面观察PL/SQL代码匿名块中非自治事务的行为:
scott@orclpdb1:orclcdb> begin
2 insert into t values('Anonymous Block');
3 NonAutonomous_Insert;
4 rollback;
5 end;
6 /
PL/SQL procedure successfully completed.
scott@orclpdb1:orclcdb> select * from t;
MSG
-------------------------
Anonymous Block
NonAutonomous Insert
2 rows selected.
scott@orclpdb1:orclcdb>
可以看到,匿名块执行的工作INSERT 由NONAUTONOMOUS_INSERT过程提交。两个数据行都已提交,所以ROLLBACK命令没有什么可以回滚。把这个过程与自治事务过程的行为加以比较:
scott@orclpdb1:orclcdb>
scott@orclpdb1:orclcdb>
scott@orclpdb1:orclcdb> delete from t;
2 rows deleted.
scott@orclpdb1:orclcdb> commit;
Commit complete.
scott@orclpdb1:orclcdb> begin
2 insert into t values('Anonymous Block');
3 Autonomous_Insert;
4 rollback;
5 end;
6 /
PL/SQL procedure successfully completed.
scott@orclpdb1:orclcdb> select * from t;
MSG
-------------------------
Autonomous Insert
1 row selected.
scott@orclpdb1:orclcdb
在此,只有自治事务中完成并已提交的工作会持久保留。匿名块中完成的INSERT 由第4行的ROLLBACK语句回滚。自治事务过程的COMMIT对匿名块中开始的父事务没有影响。
总结:如果在一个“正常”的过程中COMMIT,它不仅会持久保留自己的工作,也会使该会话中未完成的工作成为永久性的。不过,如果在一个自治事务过程中完成COMMIT,只会让这个过程本身的工作成为永久性的。