Oracle 9i & 10g编程艺术-深入数据库体系结构——第8章:事务

本文详细探讨了Oracle数据库的事务管理,包括事务控制语句(COMMIT、ROLLBACK、SAVEPOINT等)以及原子性概念,如语句级、过程级和事务级原子性。此外,还讲解了完整性约束在事务中的作用,不好的事务习惯,如在循环中提交和自动提交,以及分布式事务和自治事务的使用场景和工作原理。通过对事务的理解,有助于确保数据库的一致性和数据完整性。
摘要由CSDN通过智能技术生成

第8章                      事务

事务(Transaction)是数据库区别于文件系统的特性之一。在文件系统中,如果你正把文件写到一半,操作系统突然崩溃了,这个文件就很可能会被破坏。不错,确实还有一些“日报式”(journaled)之类的文件系统,它们能把文件恢复到某个时间点。不过,如果需要保证两个文件同步,这些文件系统就无能为力了。倘若你更新了一个文件,在更新完第二个文件之前,系统突然失败了,你就会有两个不同步的文件。

这是数据库中引入事务的主要目的:事务会把数据库从一种一致状态转变为另一种一致状态。这就是事务的任务。在数据库中提交工作时,可以确保要么所有修改都已经保存,要么所有修改都不保存。另外,还能保证实现了保护数据完整性的各种规则和检查。

在上一章中,我们从并发控制角度讨论了事务,并说明了在高度并发的数据访问条件下,根据Oracle的多版本读一致模型,Oracle事务每次如何提供一致的数据。Oracle中的事务体现了所有必要的ACID特征。ACID是以下4个词的缩写:

q         原子性(atomicity):事务中的所有动作要么都发生,要么都不发生。

q         一致性(consistency):事务将数据库从一种一致状态转变为下一种一致状态。

q         隔离性(isolation):一个事务的影响在该事务提交前对其他事务都不可见。

q         持久性(durability):事务一旦提交,其结果就是永久性的。

上一章讨论过Oracle如何得到一致性和隔离性。这里我们主要关注原子性的概念,并说明Oracle中是如何应用这个概念的。

这一章我们将讨论原子性的含义,以及Oracle中原子性对语句有什么影响。首先会介绍COMMITSAVEPOINTROLLBACK等事务控制语句,并讨论事务中如何保证完整性约束。我们会读到,如果你原来一直在其他数据库中开发,可能在事务方面养成一些坏习惯(即后面所说的“不好的事务习惯”)。这里还将介绍分布式事务和两段提交(two-phase commit2PC)。最后会分析自治事务,指出什么是自治事务以及自治事务所扮演的角色。

8.1   事务控制语句

Oracle中不需要专门的语句来“开始事务”。隐含地,事务会在修改数据的第一条语句处开始(也就是得到TX锁的第一条语句)。也可以使用SET TRANSACTIONDBMS_TRANSACTION包来显示地开始一个事务,但是这一步并不是必要的,这与其他的许多数据库不同,因为那些数据库中都必须显式地开始事务。如果发出COMMITROLLBACK语句,就会显式地结束一个事务。

注意      ROLLBACK TO SAVEPOINT命令不会结束事务!正确地写为ROLLBACK(只有这一个词)才能结束事务。

一定要显式地使用COMMITROLLBACK来终止你的事务;否则,你使用的工具/环境就会从中挑一个来结束事务。如果正常地退出SQL*Plus会话,而没有提交或回滚事务,SQL*Plus就会认为你希望提交前面做的工作,并为你完成提交。另一方面,如果你只是退出一个Pro*C程序,就会发生一个隐式的回滚。不要过分依赖这些隐式行为,因为将来这些行为可能会有改变。一定要显式地COMMITROLLBACK你的事务。

Oracle中的事务是原子性的。这说明无非两种情况:构成事务的每条语句都会提交(成为永久),或者所有语句都回滚。这种保护还延伸到单个的语句。一条语句要么完全成功,要么这条语句完全回滚。注意,我说的是“语句”回滚。如果一条语句失败,并不会导致先前已经执行的语句自动回滚。它们的工作会保留,必须由你来提交或回滚。这里谈到了语句和事务是原子性的,在具体介绍其含义之前,先来看看我们可以使用哪些事务控制语句:

q         COMMIT:要想使用这个语句的最简形式,只需发出COMMIT。也可以更详细一些,写为COMMIT WORK,不过这二者是等价的。COMMIT会结束你的事务,并使得已做的所有修改成为永久性的(持久保存)。COMMIT语句还有一些扩展用于分布式事务中。利用这些扩展,允许增加一些有意义的注释为COMMIT加标签(对事务加标签),以及强调提交一个可疑的分布式事务。

q         ROLLBACK:要想使用这个语句的最简形式,只需发出ROLLBACK。同样地,你也可以罗嗦一些,写为ROLLBACK WORK,但是二者是等价的。回滚会结束你的事务,并撤销正在进行的所有未提交的修改。为此要读取存储在回滚段/undo段中的信息,并把数据库块恢复到事务开始之前的状态(后面我将把回滚段/undo段统称为undo段,Oracle 10g 中都喜欢用这个词)。

q         SAVEPOINTSAVEPOINT允许你在事务中创建一个“标记点”(marked point),一个事务中可以有多个SAVEPOINT

q         ROLLBACK TO <SAVEPOINT>:这个语句与SAVEPOINT命令一起使用。可以把事务回滚到标记点,而不回滚在此标记点之前的任何工作。所以,可以发出两条UPDATE语句,后面跟一个SAVEPOINT,然后又是两条DELETE语句。如果执行DELETE语句期间出现了某种异常情况,而且你捕获到这个异常,并发出ROLLBACK TO SAVEPOINT命令,事务就会回滚到指定的SAVEPOINT,撤销DELETE完成的所有工作,而UPDATE语句完成的工作不受影响。

q         SET TRANSACTION:这条语句允许你设置不同的事务属性,如事务的隔离级别以及事务是只读的还是可读写的。使用手动undo管理时,还可以使用这个 来指示事务使用某个特定的undo段,不过不推荐这种做法。我们将在第9章更详细地讨论手动和自动undo管理。

就这么多,没有别的事务控制语句了。最常用的控制语句就是COMMITROLLBACKSAVEPOINT语句的用途有点特殊。Oracle在内部频繁地使用了这个语句,你会发现这语句在你的应用中可能也有用。

8.2   原子性

前面对事务控制语句做了一个简要的概述后,下面可以看看语句原子性、过程原子性和事务原子性到底有什么含义。

8.2.1             语句级原子性

考虑以下语句:

Insert into t values ( 1 );

看上去很明显,如果它由于一个约束冲突而失败,这一行就不会插入。不过,再考虑下面的例子,这里表T上的一个INSERTDELETE会触发一个触发器,它将适当地调整表T2中的CNT列:

ops$tkyte@ORA 10G > create table t2 ( cnt int );

Table created.

 

ops$tkyte@ORA 10G > insert into t2 values ( 0 );

1 row created.

 

ops$tkyte@ORA 10G > commit;

Commit complete.

 

ops$tkyte@ORA 10G > create table t ( x int check ( x>0 ) );

Table created.

 

ops$tkyte@ORA 10G > 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 ' ||

10 sql%rowcount || ' rows' );

11 end;

12 /

Trigger created.

在这种情况下,会发生什么就不那么显而易见了。如果触发器触发之后出现了错误,触发器的影响是否还存在?也就是说,如果触发器被触发,并且更新了T2,但是这一行没有插入到T中,结果会是什么?显然答案应该是,如果并没有真正在T中插入一行,我们就希望T2中的CNT列递增。幸运的是,在Oracle中,客户最初发出的语句(在这里就是INSERT INTO T)会完全成功或完全失败。这个语句是原子性的。以下可以验证这一点:

ops$tkyte@ORA 10G > set serveroutput on

ops$tkyte@ORA 10G > insert into t values (1);

I fired and updated 1 rows

1 row created.

ops$tkyte@ORA 10G > insert into t values(-1);

I fired and updated 1 rows

insert into t values(-1)

*

ERROR at line 1:

ORA-02290: check constraint (OPS$TKYTE.SYS_C009597) violated

ops$tkyte@ORA 10G > select * from t2;

CNT

----------

1

注意      使用Oracle9i Release 2及以前版本的SQL*Plus时,要想看到触发器被触发,需要在第二个插入后面增加一行代码:exec null。这是因为,在这些版本中,SQL*Plus不会在失败的DML语句之后获取和显示DBMS_OUTPUT信息。Oracle 10g 版本则不然,这个版本的SQL*Plus确实会显示DBMS_OUTPUT信息。

这样一来,T中成功地插入一行,而且我们也适当地接收到信息:I fired and updated 1 row。下一个INSERT语句违反了T上的完整性约束。此时出现了DBMS_OUTPUT信息——T上的触发器确实触发了,这个DBMS_OUTPUT信息就是证据。触发器成功地完成了T2的更新。我们可能认为现在T2CNT的值是2,但是可以看到它的值实际上为1Oracle保证最初的INSET(即导致触发器触发的插入语句)是原子性的,这个INSERT INTO T是语句,所以INSERT INTO T的任何副作用都被认为是语句的一部分。

为了得到这种语句级原子性,Oracle悄悄地在每个数据库调用外面包了一个SAVEPOINT。前面的两个INSERT实际上处理如下:

Savepoint statement1;

Insert into t values ( 1 );

If error then rollback to statement1;

Savepoint statement2;

Insert into t values ( -1 );

If error then rollback to statement2;

对于习惯于使用SybaseSQL Server的程序员来说,刚开始可能会有点摸不着头脑。在这些数据库中,情况恰恰相反。这些系统中的触发器会独立于触发语句执行。如果触发器遇到一个错误,它必须显式地回滚自己的工作,然后产生另外一个错误来回滚触发语句。否则,即使触发语句(或该语句的另外某个部分)最终会失败,触发器完成的工作也会持久保留。

Oracle中,这种语句级原子性可以根据需要延伸。在前面的例子中,如果INSERT INTO T触发了一个触发器,这个触发器会更新另一个表,而那个表也有一个触发器,它会删除第三个表(以此类推),那么要么所有工作都成功,要么无一成功。为保证这一点,无需你编写任何特殊的代码,Oracle本来就会这么做。

8.2.2             过程级原子性

有意思的是,OraclePL/SQL匿名块也当作是语句。请考虑以下存储过程:

ops$tkyte@ORA 10G > 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.

 

ops$tkyte@ORA 10G > select * from t;

no rows selected

 

ops$tkyte@ORA 10G > select * from t2;

CNT

----------

0

以上创建了一个过程,而且我们知道这个过程不会成功。在这个过程中,第二个INSERT总会失败。下面看运行这个存储过程时会发生什么情况:

ops$tkyte@ORA 10G > 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 (OPS$TKYTE.SYS_C009598) violated

ORA-06512: at "OPS$TKYTE.P", line 5

ORA-06512: at line 2

 

ops$tkyte@ORA 10G > select * from t;

no rows selected

 

ops$tkyte@ORA 10G > select * from t2;

CNT

----------

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值