轉自http://blog.csdn.net/roland_wg/article/details/4598647
http://www.oracleblog.cn/working-case/how-to-deal-with-distributed-transaction/
http://2jliu.itpub.net/post/21805/488841
http://tolywang.itpub.net/post/48/245648
http://www.oralife.cn/html/2008/410_dba_2pc_pending.html
http://psoug.org/reference/dbms_transaction.html
分布式事務,簡單來說,是指一個事務在本地和遠程執行,本地需要等待確認遠程的事務結束后,進行下一步本地的操作。如通過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 的報錯,詳細信息可見Bug2191458)
commit;
exec dbms_transaction.purge_lost_db_entry( '1.92.66874' )
Symptoms
While trying to commit or rollback a pending transaction getting error ORA-2058...Subsequently when trying to purge the pending transactions using the
procedure "dbms_transaction.purge_lost_db_entry" gives the following errors..
SQL> EXECUTE DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('5.23.2386');
BEGIN DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('5.23.2386'); END;
*
ERROR at line 1:
ORA-30019: Illegal rollback Segment operation in Automatic Undo mode
ORA-06512: at "SYS.DBMS_TRANSACTION", line 65
SQL> execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('37.16.108');
BEGIN DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('37.16.108'); END;
*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.DBMS_TRANSACTION", line 94
Cause
If the remote database no longer exists then the transaction will have to bepurged from the list of pending distributed transactions.
The transaction to be deleted is in the Prepared State.
Solution
If the command causes an ORA-2058 error to occur, it means that the remotedatabase cannot be accessed. In this case, check whether the database link to
the remote database exists and whether the remote database is shutdown.
If the remote database no longer exists then the transaction will have to be
purged from the list of pending distributed transactions.
Follow the instructions on how to purge a
distributed transaction from the database.
=================================
If the remote database cannot be accessed, a failed distributed transaction
cannot be committed or rolled back and so must be purged from the list of
pending transactions.
1. Identify the id of the transaction:
SQL> SELECT LOCAL_TRAN_ID, GLOBAL_TRAN_ID FROM DBA_2PC_PENDING;
2. Purge the transaction:
SQL> EXECUTE DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('');
SQL> COMMIT;
3. Confirm that the transaction has been purged:
SQL> SELECT LOCAL_TRAN_ID, GLOBAL_TRAN_ID FROM DBA_2PC_PENDING;
Step 2:
=====
If you get the following errors while purging transactions using "dbms_transaction.purge_lost_db_entry"
SQL> EXECUTE DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('5.23.2386');
BEGIN DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('5.23.2386'); END;
*
ERROR at line 1:
ORA-30019: Illegal rollback Segment operation in Automatic Undo mode
ORA-06512: at "SYS.DBMS_TRANSACTION", line 65
ORA-06512: at "SYS.DBMS_TRANSACTION", line 85
Fix:
===
This problem is logged as
Bug.2191458 (unpublished) UNABLE TO EXEC DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY WITH AUTO UNDO MANAGEMENT
and is worked by development.
Use the following Workaround:
You have to use local_tran_id.....
Issue commit before alter system set "_smu_debug_mode" = 4;
Follow the steps,
SQL> commit;
SQL> alter session set "_smu_debug_mode" = 4;
SQL> commit;
SQL> execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('local_tran_id');
SQL> commit;
Step 3:
=====
When executing the following procedure(dbms_transaction.purge_lost_db_entry)
to delete entries from
dba_2pc_pending one encounters the following error:
SQL> execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('37.16.108'); ==>For example..
BEGIN DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('37.16.108'); END;
*
ERROR at line 1:
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at "SYS.DBMS_TRANSACTION", line 94
Fix:
===
The transaction to be deleted is in the prepared state and has to be either
force committed or force rollback
SQL> select LOCAL_TRAN_ID,STATE from dba_2pc_pending;
LOCAL_TRAN_ID STATE
---------------------- ----------------
37.16.108 prepared
SQL> rollback force '37.16.108'; ==>For example
Rollback complete.
SQL> select LOCAL_TRAN_ID,STATE from dba_2pc_pending;
LOCAL_TRAN_ID STATE
---------------------- ----------------
37.16.108 forced rollback
SQL> COMMIT;
SQL>alter system set "_smu_debug_mode" = 4;
Rollback complete.
SQL> exec dbms_transaction.purge_lost_db_entry('37.16.108'); ==>For example
SQL> COMMIT;
set heading off pagesize 0 echo off linesize 200 feedback off trimspool on
select 'rollback force ''' || LOCAL_TRAN_ID || ''';' || chr(10) ||
'execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY(''' || LOCAL_TRAN_ID
|| ''');' || chr(10) || 'commit;' from DBA_2PC_PENDING;
exit;
EOF
execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('1506.7.4851');
commit;
execute DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY('84.4.6215898');
commit;
DBA_2PC_PENDING
Oracle會自動處理分布事務,保證分布事務的一致性,所有站點全部提交或全部回滾。
一般情況下,處理過程在很短的時間內完成,根本無法察覺到。但是,如果在commit或rollback的時候,出現了連接中斷或某個數據庫站點CRASH的情況,則提交操作可能會無法繼續,
此時DBA_2PC_PENDING和DBA_2PC_NEIGHBORS中會包含尚未解決的分布事務。
對於絕大多數情況,當恢復連接或CRASH的數據庫重新啟動后,會自動解決分布式事務,不需要人工干預。
只有分布事務鎖住的對象急需被訪問,鎖住的回滾段阻止了其他事務的使用,網絡故障或CRASH的數據庫的恢復需要很長的時間等情況出現時,才使用人工操作的方式來維護分布式事務。
手工強制提交或回滾將失去二層提交的特性,Oracle無法繼續保證事務的一致性,事務的一致性應由手工操作者保證。
對於分布式事務,給事務命名是一個好的習慣。而且在事務執行過程中,可以使用ALTER SESSION ADVISE COMMIT(ROLLBACK);語句,為手工解決分布事務提供參考信息。
當手工解決分布事務出現了沖突,比如一個站點進行了提交而另一個進行了ROLLBACK,這時,DBA_2PC_PENDING中的記錄不會清除,必須使用DBMS_TRANSACTION.PURGE_MIXED過程來清除。
如果CRASH的數據庫必須重建,或者無法再次啟動,則DBA_2PC_PENDING中的記錄也無法自動清除,需要使用DBMS_TRANSACTION.PURGE_LOST_DB_ENTRY過程來清除。
Oracle9i中,當使用上述兩個過程時,必須處於UNDO_MANAGEMENT=MANUAL的模式,這個限制條件Oracle沒有寫到文檔中。一般使用9i都會使用AUTO模式(Oracle也是這樣推薦的),
也就是說,想要清除DBA_2PC_PENDING中的信息,必須重起數據庫兩次,感覺這兩個過程的實際用處不大。
出現無法解決的分布式事務時,可能會鎖住分布式事務中涉及的表,由於Oracle無法確定哪些數據是提交過的,哪些是沒有提交的,無法確定查詢操作可見的結果集,因此,即使是查詢操作也無法在該表上執行。
使用ALTER SYSTEM DISABLE DISTRIBUTED RECOVERY,可以使Oracle不再自動解決分布事務,即使網絡恢復連接或者CRASH的數據庫重新啟動。ALTER SYSTEM ENABLE DISTRIBUTED RECOVERY恢復自動解決分布事務。
為了保證數據庫之間的SCN同步,可以采用兩種方法:在查詢數據前,執行SELECT * FROM DUAL@REMOTE或者在執行查詢前提交或回滾當前事務。
