這幾天做transaction recover測試的時候,發現個很難理解的問題。
具體問題場景如下:
1:一個standby client程序,調用userTransaction.begin()後,順序操作兩個XA resource,這兩個resource為同一個Oracle database, 不過操作使用的connection來源於不同的data source。connection1向表test中插入一條數據,connection2向表test1中插入一條數據。最後執行 userTransaction.commit()。
2:userTransaction.commit()執行的時,需要執行兩階段提交,首先是global prepare,如果所有的resource都prepare ok的話,weblogic這時候會寫入tx record(寫入到tlog中)。然後執行global commit。測試過程中,在執行完global prepare後,在global commit處設定break point,然後停止database。數據庫停止後,去除global commit的break point, 此時weblogic需要向每個resource發出commit指令。因為部分resource此時是unavailable的,weblogic無法收到每個resource commit完成的響應,該tx信息會一直保存在tlog中,等待server重起的時候recover。
3:按照正常邏輯global prepare完成後,該tx應該是只能commit,而不能rollback的,但在測試中發現,weblogic在recover的時候 ,會去rollback這個tx。
下面是測試中記錄的數據信息。
1:數據庫重起後,weblogic重起前,可以看到database中該tx信息如下:
2:weblogic tlog中的信息,
+------------------------------------------------------------------------------+
| Transaction Log Dump | |
+------------------------------------------------------------------------------+
| Class Name = weblogic.transaction.internal.ResourceCheckpoint |
| Object = ResourceCheckpoint={OracelXADS, OracleXADS1} |
+------------------------------------------------------------------------------+
3:weblogic重起後,可以看到該tx branch在recover的時候被rollback,
####<Nov 21, 2008 11:20:33 AM CST> <Debug> <debug@XAResourceDescriptor> <why> <AdminServer> <[STANDBY] ExecuteThread: '4' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1227237633250> <000000> <to rollback tx, xid: BEA1-0000669563242B7A1CFC-4F726163656C58414453>
這個信息是我debug出來的,weblogic會依據xid(branchid),調用 XAResource.rollback(Xid xid), 將該事務分支rollback。現在看到的問題是:4F7261636C655841445331分支已經commit了,我們調用 XAResource.rollback(Xid xid), rollback 0000669563242B7A1CFC-4F726163656C58414453後,4F7261636C655841445331怎麼也被 rollback了呢?
Note: 在weblogic recover的時候,XAResource需要執行後端的存儲過程,如果對應用戶沒有權限的話,weblogic server的日志裡能看到如下信息:
####<Nov 20, 2008 7:12:24 PM CST> <Debug> <JDBCDriverLogging> <why> <AdminServer> <[STANDBY] ExecuteThread: '3' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1227179544296> <000000> <SQLException: SQLState(65000) vendor code(6550)>
####<Nov 20, 2008 7:12:24 PM CST> <Debug> <JDBCDriverLogging> <why> <AdminServer> <[STANDBY] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1227179544296> <000000> <java.sql.SQLException: ORA-06550: line 1, column 7:
PLS-00201: identifier 'SYS.DBMS_SYSTEM' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
碰到這種錯誤的時候,執行如下的SQL:grant execute on sys.dbms_system to system;
########################後續#####################
經過半天的調查,終於找到問題的原因了。問題出在Oracle XA Driver上,我們使用來自於不同datasource的XAConnection對同一個database操作的時候,我們把它們當作兩個事務分支,分支ID如下:
Branch 1: 4F7261636C655841445331
Branch 2: 4F7261636C65584144533
如果這兩個事務分支分別指向不同的database的話,global prepare時,xaResource.prepare()的結果應該為OK(prepare的時候,database工作正常)。而我們上面的測試中,因為操作的是統一database,其prepare的結果分別是:OK, READ_ONLY(數據庫端認為該TX branch對應的操作不涉及數據修改)。返回READ_ONLY的時候,因為database認為該 tx branch不涉及數據修改,所以直接將它commit了。這也就解釋了上面的疑問,global commit沒有執行的時候,怎麼就有tx branch被commit了。
下面再看看,為什麼tlog中只能看到check point信息,而沒有記錄tx信息。在global prepare中,所有的參與該tx的resource的prepare結果返回後,weblogic需要根據vote結果決定進行global commit or global rollback。如果所有resource prepare都沒問題的話,則將進行global commit。而在global commit之前,weblogic需要判斷是否要將該tx信息記錄到tlog中,而是記錄tx的依據就是vote結果為0(OK)的resource個數是否大於1。大於1,則記入tlog,否則不做記錄。我們上面的測試中,因為只涉及兩個resource,而這兩個resource的vote結果分別為:0(OK), 3(READ_ONLY),所以該tx信息沒有被記錄到tlog中。
weblogic在recover的時候,首先檢查tlog中的tx信息,如果存在tx,則讀取tx信息,並將該tx放入到當前 server的txMap中。然後調用xaResource.recover(),該方法的執行結果是,resource manager(如database, jms server)返回自己手裡的pending transaction的xid list。 weblogic遍歷該xid list,如果發現某個xid在當前txMap中,則忽略它(該tx將會被其他線程commit),那些不在txMap中的xid將會被 rollback,通過xaResource.rollback(Xid xid)實現。在我們上面的測試中,因為tx沒有被記錄到tlog中,所以weblogic在recover的時候會將database返回的xid: 0000669563242B7A1CFC-4F726163656C58414453 rollback。由於Oracle Driver的特殊處理,兩個connection上的兩個tx branch被合並成了一個branch(另一個被它認為是個READ_ONLY),所以在rollback的時候,test, test1兩表上的數據都被rollback了。
在使用兩個不同的database測試中,能夠看到tlog中的tx信息,weblogic server重啟後,也能看到tx的commit。tlog信息如下:
最後再說一下weblogic執行global prepare時,resource的prepare順序:
1:remote xa resource
2:local xa resource
3:non-XA resource( LLR,對於Emulate 2PC的nonXA reosurce, 它的prepare結果直接就是OK,其實是個假prepare)
所謂的remote, local不是只resource managed的位置,而是指resource對應的coordinator的位置。