分散式事務故障處理

perfychi發表於2012-09-10
分散式事務故障處理

分散式事務,簡單來說,是指一個事務在本地和遠端執行,本地需要等待確認遠端的事務結束後,進行下一步本地的操作。如透過dblink update遠端資料庫的一行記錄,如果在執行過程中網路異常,或者其他事件導致本地資料庫無法得知遠端資料庫的執行情況,此時就會發生in doublt的報錯。此時需要dba介入,且需要分多種情況進行處理。

分散式事務在commit提交時候,會經歷3個階段:
1.PREPARE PHASE:
1.1 決定哪個資料庫為commit point site。(注,引數檔案中commit_point_strength值高的那個資料庫為commit point site)
1.2 全域性協調者(Global Coordinator)要求所有的點(除commit point site外)做好commit或者rollback的準備。此時,對分散式事務的表加鎖。
1.3 所有分散式事務的節點將它的scn告知全域性協調者。
1.4 全域性協調者取各個點的最大的scn作為分散式事務的scn。(eygle在這篇文章中也測試過)
至此,所有的點都完成了準備工作,我們開始進入COMMIT PHASE階段,此時除commit point site點外所有點的事務均為in doubt狀態,直到COMMIT PHASE階段結束。

如果資料庫在此階段出現問題,我們查詢(假設遠端資料庫為commit point site,且本地資料庫為Global Coordinator):

本地:
select local_tran_id,state from dba_2pc_pending
LOCAL_TRAN_ID          STATE
--------------------
-- ----------------
2.12.64845              collecting
遠端:
select local_tran_id,state from dba_2pc_pending
no rows selected

即表示本地資料庫要求其他點做好commit或者rollback準備,現在正在“收集”其他點的資料庫的返回資訊,但是遠端資料庫未知狀態(in doubt)。我們需要將本地的Global Coordinator的狀態清除掉:

execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('local_tran_id');

或者我們在查詢的時候發現是如下的狀態:

本地:
select local_tran_id,state from dba_2pc_pending
LOCAL_TRAN_ID          STATE
--------------------
-- ----------------
2.12.64845             prepared
遠端:
select local_tran_id,state from dba_2pc_pending
no rows selected

即表示本地Global Coordinator已經做好準備,已經將分散式鎖放到各個事務的表上,但是遠端資料庫的狀態再次未知(in doubt),我們需要手工的將本地的transaction rollback掉,並且清除分散式事務資訊:

本地:
rollback force 'local_tran_id';
execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('local_tran_id');

2.COMMIT PHASE:
2.1 Global Coordinator將最大scn傳到commit point site,要求其commit。
2.2 commit point嘗試commit或者rollback。分散式事務鎖釋放。
2.3 commit point通知Global Coordinator已經commit。
2.4 Global Coordinator通知分散式事務的所有點進行commit。

如果資料庫在此階段出現問題,我們查詢

本地:
select local_tran_id,state from dba_2pc_pending
LOCAL_TRAN_ID          STATE
--------------------
-- ----------------
2.12.64845             prepared
遠端:
select local_tran_id,state from dba_2pc_pending
LOCAL_TRAN_ID          STATE
--------------------
-- ----------------
1.92.66874             commited

即遠端資料庫可能已經commit,但是本地Global Coordinator未知遠端資料庫的狀態,還是處於prepare的狀態。我們需要在如下處理:

本地:
commit force 'local_tran_id';
execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('local_tran_id');
遠端:
execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('local_tran_id');

或者我們在查詢的時候發現是如下的狀態:

本地:
select local_tran_id,state from dba_2pc_pending
LOCAL_TRAN_ID          STATE
--------------------
-- ----------------
2.12.64845            commited
遠端:
select local_tran_id,state from dba_2pc_pending
LOCAL_TRAN_ID          STATE
--------------------
-- ----------------
1.92.66874             commited

即遠端資料庫和本地資料庫均已經完成commit,但是分散式事務的資訊尚未清除,我們需要在本地和遠端執行:

本地:
execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('local_tran_id');
遠端:
execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('local_tran_id');

3.FORGET PHASE:
3.1 參與的點通知commit point site他們已經完成commit,commit point site就能忘記(forget)這個事務。
3.2 commit point site在遠端資料庫上清除分散式事務資訊。
3.3 commit point site通知Global Coordinator可以清除本地的分散式事務資訊。
3.4 Global Coordinator清除分散式事務資訊。

此時如果出現問題,我們查詢:

本地:
select local_tran_id,state from dba_2pc_pending
LOCAL_TRAN_ID          STATE
--------------------
-- ----------------
2.12.64845            commited
遠端:
select local_tran_id,state from dba_2pc_pending
no rows selected

即遠端commit point site已經完成commit,通知Global Coordinator清除本地的分散式事務資訊,但是Global Coordinator沒有收到該資訊。我們需要這樣處理:

本地:
execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('local_tran_id');

綜上,分散式事務的依次狀態為:

綜上,分散式事務的依次狀態為:
phase       local_state    remote_state       action
----------- ---------- ------------------  -------------------
prepare     collecting       /              本地DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY
            prepared         /              本地rollback force後PURGE_LOST_DB_ENTRY
 
commit      prepared        commited        本地commit force後本地和遠端均PURGE
            commited        commited        本地和遠端均PURGE_LOST_DB_ENTRY
            
forget      commited         /              本地PURGE_LOST_DB_ENTRY

另,當我們遇到使用rollback/commit force的時候,無法正常的清除分散式事務的資訊,會報錯ORA-02058: no prepared transaction found with ID X.XX.XXXXX時,我們需要透過手工方式來清除該資訊。(注,以下方式修改資料字典,存在風險,使用前請備份好你的資料庫)

情況1,在dba_2pc表中還有事務記錄,但是實際已經不存在該事務了:

select local_tran_id, state from dba_2pc_pending;
 
      
LOCAL_TRAN_ID          STATE
       --------------------
-- ----------------
      
1.92.66874             prepared
 
(注:
'1.92.66874' 的結構為rbs#, slot#, wrap#,此事務在rollback segment #1)
我們再用如下語句找出使用
rollback segment #1且狀態是active的transaction:
 
SELECT KTUXEUSN, KTUXESLT, KTUXESQN, /* Transaction ID */
        
KTUXESTA Status,
        
KTUXECFL Flags
 
FROM x$ktuxe
 
WHERE ktuxesta!='INACTIVE'
        
AND ktuxeusn= 1; <== 這是rollback segment#,即rbs#
 
 
no rows selected

因此我們在rollback force的時候會報錯:

ORA-02058: no prepared transaction found with ID 1.92.66874

我們需要如下處理:

set transaction use rollback segment SYSTEM;
 
delete from sys.pending_trans$
 
where local_tran_id = '1.92.66874';
 
delete from sys.pending_sessions$ where local_tran_id = '1.92.66874';
 
delete from sys.pending_sub_sessions$ where local_tran_id = '1.92.66874';
 
commit;

情況2,這種情況比較少見,在dba_2pc表中無法查到分散式事務資訊,但是實際上卻是存在該分散式事務的:

我們在alertlog中可以看到:

ORA-1591: lock held by in-doubt distributed transaction 1.92.66874

我們查詢dba_2pc的表,發現沒有分散式事務資訊:

select local_tran_id, state from dba_2pc_pending
where local_tran_id='1.92.66874';
 
no rows selected

但是去查實際的rollback segment資訊,卻發現有prepared狀態的分散式事務存在:

SELECT KTUXEUSN, KTUXESLT, KTUXESQN, /* Transaction ID */
      
KTUXESTA Status,
      
KTUXECFL Flags
FROM x$ktuxe
WHERE ktuxesta!='INACTIVE'
      
AND ktuxeusn= 1;
 
 
KTUXEUSN   KTUXESLT   KTUXESQN STATUS           FLAGS
--------
-- ---------- ---------- ---------------- ------------------------
        
1         92      66874 PREPARED         SCO|COL|REV|DEAD

我們無法做commit force或者rollback force:

rollback force '1.92.66874';
 
ORA-02058: no prepared transaction found with ID 1.92.66874

我們用如下的方式手工清理:

alter system disable distributed recovery;
 
    
insert into pending_trans$ (
        
LOCAL_TRAN_ID,
        
GLOBAL_TRAN_FMT,
        
GLOBAL_ORACLE_ID,
        
STATE,
        
STATUS,
        
SESSION_VECTOR,
        
RECO_VECTOR,
        
TYPE#,
        
FAIL_TIME,
        
RECO_TIME)
    
values( '1.92.66874', /* <== 此處為你的local tran id */
        
306206,                  /*                                         */
        
'XXXXXXX.12345.1.2.3',   /*  這些值不必更改,   */
        
'prepared','P',          /*  是靜態引數,可以直接   */
        
hextoraw( '00000001' )/*  在這個sql中使用                             */
        
hextoraw( '00000000' )/*                                         */
        
0, sysdate, sysdate );
 
    
insert into pending_sessions$
    
values( '1.92.66874',/* <==此處為你的local tran id  */
        
1, hextoraw('05004F003A1500000104'),
        
'C', 0, 30258592, '',
        
146
      
);
 
    
commit;
 
    
commit force '1.92.66874';
    
    此時如果
commit force還是出現報錯,需要繼續執行:
 
      
delete from pending_trans$ where local_tran_id='1.92.66874';
      
delete from pending_sessions$ where local_tran_id='1.92.66874';
      
commit;
      
alter system enable distributed recovery;
 
    此時如果沒有報錯,則執行以下語句:
 
      
alter system enable distributed recovery;
 
      
and purge the dummy entry from the dictionary, using
 
      
connect / as sysdba
 
      
alter session set "_smu_debug_mode" = 4;
      (注:如果使用
autoundo管理方式,需要執行此步驟,此步驟能避免在後續執行purge_lost_db_entry出現ORA-01453 的報錯,詳細資訊可見Bug 2191458
 
      
commit
 
      
exec dbms_transaction.purge_lost_db_entry( '1.92.66874' )

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/27042095/viewspace-743020/,如需轉載,請註明出處,否則將追究法律責任。

相關文章