打算写一系列的文章介绍11g的新特性和变化。
Oracle11g提供了撤销事务的功能,可以撤销一个已经提交的事务。
这一篇介绍撤销级联事务。
Oracle11新特性——撤销事务(一):http://yangtingkun.itpub.net/post/468/419695
上面一篇简单介绍了如何撤销一个事务,那个例子中,被撤销事务和其他事务没有关联。如果撤销一个事务的时候发现被撤销事务和其他事务级联,那么会有多种情况。
SQL> CONN YANGTK/yangtk@172.0.2.61/test11g.netdb已连接。
SQL> CREATE TABLE T_FLASH_TRANS (ID NUMBER, NAME VARCHAR2(30));
表已创建。
SQL> INSERT INTO T_FLASH_TRANS VALUES (1, 'A');
已创建 1 行。
SQL> INSERT INTO T_FLASH_TRANS VALUES (2, 'B');
已创建 1 行。
SQL> COMMIT;
提交完成。
SQL> UPDATE T_FLASH_TRANS SET NAME = NAME || '1';
已更新2行。
SQL> INSERT INTO T_FLASH_TRANS VALUES (3, 'C');
已创建 1 行。
SQL> COMMIT;
提交完成。
SQL> UPDATE T_FLASH_TRANS SET NAME = NAME || '2' WHERE ID = 1;
已更新 1 行。
SQL> INSERT INTO T_FLASH_TRANS VALUES (4, 'D');
已创建 1 行。
SQL> COMMIT;
提交完成。
SQL> CONN SYS/test@172.0.2.61/TEST11G.NETDB AS SYSDBA已连接。
SQL> SELECT XID, OPERATION, UNDO_SQL
2 FROM FLASHBACK_TRANSACTION_QUERY
3 WHERE TABLE_NAME = 'T_FLASH_TRANS';
XID OPERATION UNDO_SQL
---------------- ---------- ------------------------------------------------------------------------
0004001A00000344 INSERT delete from "YANGTK"."T_FLASH_TRANS" where ROWID = 'AAARskAAFAAACY3AAC';
0004001A00000344 UPDATE update "YANGTK"."T_FLASH_TRANS" set "NAME" = 'B' where ROWID = 'AAARskAA
0004001A00000344 UPDATE update "YANGTK"."T_FLASH_TRANS" set "NAME" = 'A' where ROWID = 'AAARskAA
0006002F00000345 INSERT delete from "YANGTK"."T_FLASH_TRANS" where ROWID = 'AAARskAAFAAACY3AAD';
0006002F00000345 UPDATE update "YANGTK"."T_FLASH_TRANS" set "NAME" = 'A1' where ROWID = 'AAARskA
000A005000000343 INSERT delete from "YANGTK"."T_FLASH_TRANS" where ROWID = 'AAARskAAFAAACY3AAB';
000A005000000343 INSERT delete from "YANGTK"."T_FLASH_TRANS" where ROWID = 'AAARskAAFAAACY3AAA';
已选择7行。
下面对第一个更新进行撤销:
SQL> DECLARE
2 V_XID XID_ARRAY;
3 BEGIN
4 V_XID := SYS.XID_ARRAY('0004001A00000344');
5 DBMS_FLASHBACK.TRANSACTION_BACKOUT(1, V_XID);
6 END;
7 /
DECLARE
*第 1 行出现错误:
ORA-55504: NOCASCADE 模式下的事务处理冲突
ORA-06512: 在 "SYS.DBMS_FLASHBACK", line 37
ORA-06512: 在 "SYS.DBMS_FLASHBACK", line 70
ORA-06512: 在 line 5
撤销出错,这是由于要撤销第二个事务,这个事务对ID为1的记录进行了更新,但是在随后的事务中又对这条记录进行了修改。也就是说这个事务是后面事务的基础,这时没有办法仅仅撤销前面的事务而不影响后面的事务。
对于这种情况,Oracle有三种不同的解决方法,其中最好理解的方法是CASCADE方式:
SQL> DECLARE
2 V_XID XID_ARRAY;
3 BEGIN
4 V_XID := SYS.XID_ARRAY('0004001A00000344');
5 DBMS_FLASHBACK.TRANSACTION_BACKOUT(1, V_XID, DBMS_FLASHBACK.CASCADE);
6 END;
7 /
PL/SQL 过程已成功完成。
SQL> SELECT * FROM YANGTK.T_FLASH_TRANS;
ID NAME
---------- ------------------------------
1 A
2 B
使用CASCADE方式,会根据事务提交的相反顺序,依次撤销所有关联的事务。
在这个例子中,Oracle不但撤销了第一个UPDATE事务,而且撤销了随后的UPDATE和INSERT事务。
下面看第二种方式,NONCONFLICT_ONLY。在采用这种方式之前,先要说明一下,TRANSACTION_BACKOUT过程是事务性的,也就是说,如果发现撤销事务后,得到的不是预期的结果,还可以通过ROLLBACK来回滚事务撤销操作本身:
SQL> ROLLBACK;
回退已完成。
SQL> SELECT * FROM YANGTK.T_FLASH_TRANS;
ID NAME
---------- ------------------------------
1 A12
2 B1
3 C
4 D
下面采用NONCONFLICT_ONLY方式进行事务撤销:
SQL> DECLARE
2 V_XID XID_ARRAY;
3 BEGIN
4 V_XID := SYS.XID_ARRAY('0004001A00000344');
5 DBMS_FLASHBACK.TRANSACTION_BACKOUT(1, V_XID, DBMS_FLASHBACK.NONCONFLICT_ONLY);
6 END;
7 /
PL/SQL 过程已成功完成。
SQL> SELECT * FROM YANGTK.T_FLASH_TRANS;
ID NAME
---------- ------------------------------
1 A12
2 B
4 D
对于NONCONFLICT_ONLY方式,Oracle会回滚指定事务中没有被关联的部分。比如这个例子中,UPDATE语句被后面的事务关联,但是INSERT语句并没有被关联。因此,撤销了事务中的INSERT语句,对于UPDATE语句,只有关联的那条数据没有被撤销,而没有被关联的数据则被撤销成功。这种方式可以保证数据库的一致性,但是会损失事务的完整性。
Oracle文档上还描述了一种方式NOCASCADE_FORCE:
SQL> ROLLBACK;
回退已完成。
SQL> SELECT * FROM YANGTK.T_FLASH_TRANS;
ID NAME
---------- ------------------------------
1 A12
2 B1
3 C
4 D
SQL> DECLARE
2 V_XID XID_ARRAY;
3 BEGIN
4 V_XID := SYS.XID_ARRAY('0004001A00000344');
5 DBMS_FLASHBACK.TRANSACTION_BACKOUT(1, V_XID, DBMS_FLASHBACK.NOCASCADE_FORCE);
6 END;
7 /
PL/SQL 过程已成功完成。
SQL> SELECT * FROM YANGTK.T_FLASH_TRANS;
ID NAME
---------- ------------------------------
1 A12
2 B
4 D
根据文档上的描述,个人感觉采用这种方式的撤销后,ID为1的记录NAME应该为A而不是A12,现在的这种结果与NONCONFLICT_ONLY方式没有差别,怀疑目前的结果是个bug。不过实际如何只能等Oracle推出补丁或者声明bug才能了解。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/4227/viewspace-69549/,如需转载,请注明出处,否则将追究法律责任。