到目前为止,介绍的全部功能均不会直接将数据恢复为“之前”的样子。闪回查询只是查看,闪回数据归档只是延伸了闪回查询的时间窗口,闪回事务查询虽然提供了撤销SQL,可是否执行及如何执行还须要管理员进一步手动操做。sql
如果管理员决定撤销某个或某些事务,Oracle提供一个专门用来撤销事务的工具——闪回事务。工具
闪回事务又名撤销事务(Backout Transaction),可以撤销一个或多个事务的修改,其功能由一个名为DBMS_FLASHBACK.TRANSACTION_BACKOUT的存储过程实现。该存储过程的工做原理是自动分析重作日志,挖掘出变动前的值用以构建撤销SQL(Undo SQL),而后执行撤销SQL最后达到撤销事务的目的。为了该功能能够正常使用,至少须要事先启用主键补充日志。另外,为了可以跟踪外键依赖还须要启用外键补充日志。日志
在继续讨论此功能前,首先应了解一个概念:事务的依赖性。好比,两个事务TX1和TX2,若符合如下3个条件的任意一个就能够认为TX2依赖TX1:事务
(1)WAW依赖(Write After Write),即在TX1修改了表的某行以后,TX2又修改了同一行。get
(2)主键依赖,即在一张拥有主键的表中TX1首先删除了一行,以后TX2又插入了具备相同主键值的另外一行。flash
(3)外建依赖,即因为TX1的修改(insert或update)而产生了新的可被外键参考的字段值,以后TX2修改(insert或update)外键字段时利用了TX1所产生的字段值。it
了解事务依赖性有助于解决在撤销事务时遇到的矛盾,以主键依赖为例,试想若直接将事务TX1撤销而且不理会事务TX2,岂不是会出现主键值重复的行!io
TRANSACTION_BACKOUT存储过程的OPTIONS参数就是为了解决事务依赖性问题而存在的,在该参数上管理员可使用4种撤销事务的方案,假设被撤销的事务是TX1,若其具备依赖事务,则称为TX2:table
(1)NOCASCADE,TX1不能够被任何其余事务依赖(即TX2不存在),不然撤销操做报错。class
(2)CASCADE,将TX1连同TX2一块儿撤销。
(3)NOCASCADE_FORCE,忽略TX2,直接执行TX1的撤销SQL将TX1撤销,若是没有约束上的冲突,操做将成功,不然约束报错致使撤销操做失败。
(4)NONCONFILICT_ONLY,在不影响TX2的前提下,撤销TX1的修改。与NOCASCADE_FORCE的不一样点在于会首先过滤一下TX1的撤销SQL,确保它们不会做用在TX2修改的行上。
接下来以WAW依赖为例详细说明,好比有一张表的原有数据以下所示,只有3行且没有约束:
ID
----------
1
2
3
接下来前后发起事务TX1和TX2仅修改该表。在事务TX1(更新了3行)执行后其数据变动为:
ID
----------
11
22
33
以后,在事务TX2(更新了两行,第一行没有修改)执行后其数据变动为:
ID
----------
11
222
333
此例为典型的WAW依赖,TX2依赖TX1。
如今计划将事务TX1撤销,那么使用不一样的OPTIONS将产生不一样的结果。
若采用NOCASCADE结果是抛出错误“ORA-55504: Transaction conflicts in NOCASCADE mode”,表内容依然是:
ID
----------
11
222
333
若采用CASCADE,表的内容恢复到TX2和TX1均未执行的状态:
ID
----------
1
2
3
若采用NOCASCADE_FORCE,TX2的结果不受影响,但被TX1修改的第一行回滚了,闪回事务没有尊重TX1的事务原子性。表的内容变为:
ID
----------
1
222
333
也许读者会感到奇怪,根据NOCASCADE_FORCE的定义,会在全部行上执行撤销SQL,那为何第2和第3行的内容没有回到TX1执行以前呢?缘由是此例中撤销SQL的where语句中还包含ID字段的值,这是启用了主键补充日志的结果:
update <表名> set "ID" = '1' where "ID" = '11' and ROWID = <第1行ROWID>;
update <表名> set "ID" = '2' where "ID" = '22' and ROWID = <第2行ROWID>;
update <表名> set "ID" = '3' where "ID" = '33' and ROWID = <第3行ROWID>;
没记错的话第2和第3行的ID字段已经被TX2分别修改成222和333了,因此虽然执行了3条撤销SQL,但只有第1行获得了修改。
若采用NONCONFILICT_ONLY,在此例中将产生与NOCASCADE_FORCE同样的结果:
ID
----------
1
222
333
读者须要明白本状况中的撤销SQL应该只有一条:
update <表名> set "ID" = '1' where "ID" = '11' and ROWID = <第1行ROWID>;
虽然最后的结果是相同的,可是与NOCASCADE_FORCE所作的尝试是不一样的,和TX2有关的对第2行、第3行的更改命令首先被过滤了。试想若在事务TX2以后还有一个事务TX3又将第3行的ID字段改回33,再使用NOCASCADE_FORCE和NONCONFILICT_ ONLY将TX1闪回,结果将会怎样。
使用DBMS_FLASHBACK.BACKOUT_TRANSACTION的步骤以下:
(1)将须要撤销的事务的事务号或事务名载入对应的VARRAY集合变量。
(2)以NOCASCADE方式调用BACKOUT_TRANSACTION。若是报错,再从另外3种方式中选择一个调用BACKOUT_TRANSACTION。
(3)查看闪回事务操做的报告。
(4)最后决定提交或回滚。
下面是一个展现闪回事务战斗力的例子,做为本节的结尾。
首先确认一下201号员工的薪水是13000美圆:
SQL> select salary from hr.employees where employee_id=201;
SALARY
------
13000
而后,将全体员工的工资涨500 %,这是一次人为错误,201号员工的收入变为78000美圆了:
SQL> update hr.employees set salary=salary*5;
107 rows updated.
SQL> commit;
Commit complete.
紧接着,人事管理应用发出一个正常的操做将201号员工的工资上浮10 %,这里笔者用SQL*Plus模拟HR应用:
SQL> update hr.employees set salary=salary*1.1 where employee_id=201;
1 row updated.
SQL> commit;
Commit complete.
显然HR应用的本意是让201号员工得到13000美圆的110%,即14300美圆月薪,可是通过前一次错误update的修改,现在该员工的薪水是85800美圆:
SQL> select salary from hr.employees where employee_id=201;
SALARY
----------
71500
不久以后,工做人员发现全部员工的薪水高得反常,管理员受理以后经过闪回事务查询查询发现最近(15分钟以内)在hr.employees表上的事务有两个:
SQL> select distinct xid,commit_scn
2 from flashback_transaction_query
3 where table_owner='HR' and
4 table_name='EMPLOYEES' and
5 commit_timestamp > systimestamp - interval '15' minute
6 order by commit_scn;
XID COMMIT_SCN
---------------- ----------
0A00160094020000 1277129
0900070068030000 1277301
而后再利用闪回事务查询观察FLASHBACK_TRANSACTION_QUERY.UNDO_SQL字段,了解到COMMIT_SCN号是127712九、事务号为0A00160094020000的事务很不正常,不但更新了全部员工的SALARY,并且金额太大(由于撤销SQL中的SALARY很低,而当前SALARY很高):
SQL> select undo_sql from flashback_transaction_query
2 where commit_scn='1277129';
UNDO_SQL
----------------------------------------------------------------------------
update "HR"."EMPLOYEES" set "SALARY" = '3000' where ROWID = 'AAAR5pAAFAAAADPABh';
update "HR"."EMPLOYEES" set "SALARY" = '3100' where ROWID = 'AAAR5pAAFAAAADPABg';
update "HR"."EMPLOYEES" set "SALARY" = '2800' where ROWID = 'AAAR5pAAFAAAADPABf';
update "HR"."EMPLOYEES" set "SALARY" = '3200' where ROWID = 'AAAR5pAAFAAAADPABe';
update "HR"."EMPLOYEES" set "SALARY" = '3900' where ROWID = 'AAAR5pAAFAAAADPABd';
update "HR"."EMPLOYEES" set "SALARY" = '4000' where ROWID = 'AAAR5pAAFAAAADPABc';
update "HR"."EMPLOYEES" set "SALARY" = '2500' where ROWID = 'AAAR5pAAFAAAADPABb';
...省略100行
注意撤销SQL中赋予SALARY字段的值正是事务0A00160094020000执行前的值。现决定用TRANSACTION_BACKOUT闪回该事务,使SALARY恢复正常值:
SQL> declare
2 xids sys.xid_array;
3 begin
4 xids := sys.xid_array('0A00160094020000');
5 dbms_flashback.transaction_backout(1,xids,options=>dbms_flashback.nocascade);
6 end;
7 /
第5行中存储过程的第二个参数是一个容纳事务号的VARRAY集合变量,第一个参数表示VARRAY内事务号的数量,本例中只有一个事务须要撤销,因此等于1。
由于WAW依赖性,这样执行会失败:
declare
*
ERROR at line 1:
ORA-55504: Transaction conflicts in NOCASCADE mode
ORA-06512: at "SYS.DBMS_FLASHBACK", line 37
ORA-06512: at "SYS.DBMS_FLASHBACK", line 70
ORA-06512: at line 5
如今最符合逻辑的作法是使用casecade方式将两个事务所有撤销,修改options参数后从新执行:
SQL> declare
2 xids sys.xid_array;
3 begin
4 xids := sys.xid_array('0A00160094020000');
5 dbms_flashback.transaction_backout(1,xids,options=>dbms_flashback.cascade);
6 end;
7 /
PL/SQL procedure successfully completed.
待执行完毕后查看闪回事务的报告:
SQL> select xid,dependent_xid,backout_mode from dba_flashback_txn_state;
XID DEPENDENT_XID BACKOUT_MODE
---------------- ---------------- ----------------
0900070068030000 CASCADE
0A00160094020000 0900070068030000 CASCADE
发现事务0900070068030000也被撤销了。
查看201号员工的薪水:
SQL> select salary from hr.employees where employee_id=201;
SALARY
----------
13000
果真回到了最初的13000美圆。如今全部员工的薪水应该都恢复正常了。
DBMS_FLASHBACK.TRANSACTION_BACKOUT是用一个新的事务执行撤销SQL的,如今应执行commit或rollback命令确认或取消闪回事务的结果,这里使用commit:
SQL> commit;
Commit complete.
闪回事务至此结束。