閃回事務(Flashback Transaction)

到目前爲止,介紹的全部功能均不會直接將數據恢復爲「之前」的樣子。閃回查詢只是查看,閃回數據歸檔只是延伸了閃回查詢的時間窗口,閃回事務查詢雖然提供了撤銷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.

閃回事務至此結束。

相關文章
相關標籤/搜索