分散式事務,簡單來說,是指一個事務在本地和遠端執行,本地需要等待確認遠端的事務結束後,進行下一步本地的操作。如透過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的狀態清除掉:
或者我們在查詢的時候發現是如下的狀態:
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表中還有事務記錄,但是實際已經不存在該事務了:
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的時候會報錯:
我們需要如下處理:
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中可以看到:
我們查詢dba_2pc的表,發現沒有分散式事務資訊:
where local_tran_id='1.92.66874';
no rows selected
但是去查實際的rollback segment資訊,卻發現有prepared狀態的分散式事務存在:
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:
ORA-02058: no prepared transaction found with ID 1.92.66874
我們用如下的方式手工清理:
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;
(注:如果使用auto的undo管理方式,需要執行此步驟,此步驟能避免在後續執行purge_lost_db_entry出現ORA-01453 的報錯,詳細資訊可見Bug 2191458)
commit;
exec dbms_transaction.purge_lost_db_entry( '1.92.66874' )