引言
我們都知道數據庫是有事務隔離級別的,在讀未提交的情況下,會出現臟讀的現象,即某個會話會讀到其他會話還沒提交的事務。oracle默認的隔離級別是讀提交,是不會出現臟讀的現象,本次故障就是由于應用讀到了沒能提交的事務,導致的交易異常。
問題現象
某天晚上,公司的存儲發生了故障,導致數據庫重啟了,本來也沒啥,過了幾天項目組的同事找到我,說有一筆賬單異常了,讓幫忙從數據庫上查一下,情況是這樣的:這個交易涉及三個系統A、B、C。A系統向B系統發起轉賬請求,B系統記賬,并轉發給C系統,C系統轉賬完成回復B系統,B系統更新剛才的賬單狀態為已完成,并返回給A系統。A系統如果在超時時間內沒收到B系統返回,會發起二次查詢,查詢B系統的這條賬單的狀態。當時出現的問題是C系統完成了轉賬,并有記錄,A系統是在超時時間內沒收到B系統返回,發起的二次查詢發現B系統的賬單狀態時已完成轉賬。B系統自己數據庫記錄的這條轉賬狀態為未完成。
問題分析
步驟1:捋捋邏輯和時間線
針對這個問題,根據應用日志先捋下時間線:
22:53:46,345 =====》B系統數據庫插入轉賬記錄,并標記轉賬狀態為未完成。這是個insert操作。
22:53:46,589 =====》B系統接到C系統轉賬成功的消息,更新數據庫中轉賬狀態為已完成,這是個update操作。
22:53:57,519 =====》B系統接到A系統的二次查詢,顯示剛才的轉賬狀態為已完成,這是個select操作。
第二天 =====》項目組登錄B數據庫,查詢這條交易轉賬狀態為未完成。這是個select操作。
針對這個特殊情況,我跟項目組討論了以下懷疑的點:
a、表中是否有多條重復記錄? =======》根據唯一鍵交易流水號查詢,無重復值
b、人為修改的? ===============》不可能,堡壘機上沒查到相關的操作。與相關維護人員確認,沒人動過。
c、A系統是發起二次查詢復用了insert和update的連接?======》這是串臺了嗎?腦洞也太大了,而且二次查詢發起的服務器和前面的insert的服務器都不是一臺。
d、應用邏輯問題? ============》這套系統是公司十分重要的系統,已經運行多年,從來沒出現過這總情況。
步驟2:日志挖掘
A系統二次查詢和項目組查詢的結果不一致,到底update語句執行了嗎?雖然應用日志里有記錄,但是這個問題還是需要確認下,我想起了可以通過挖掘redo日志的方法確認,在提交了的事務一定在redolog里有記錄,先從帶庫對日志進行了恢復,在演練環境進行了日志挖掘,具體挖掘步驟不說了,回頭可以額單獨寫一篇文章介紹。
這個表是有唯一鍵的,根據唯一鍵對挖掘的日志進行了搜索,只收到了insert的那一句,并未查到update,既然redo里沒有,這說明update沒提交!!!!
這個可以解釋第二天查詢的交易狀態為未完成,但是沒法解釋A系統的二次查詢的轉賬狀態為已完成這個情況啊。而且該表中有一個字段是TIME,該字段在insert的時候會插入執行的時間戳,
在update的時候,也會更新成最新的時間戳,應用日志中顯示,A系統二次查詢出的該條交易的TIME字段的值和應用日志里記錄的update的時候的值都是22:53:46.589,這說明應用日志里的update肯定是執行了,A系統二次查詢就是查詢的這條記錄。
步驟3:深入oracle事務提交原理
問題分析到這個是,感覺陷入死胡同了,一方面A系統二次查詢確實是查到了B系統數據庫執行update后的這條記錄,另一方面redo日志里又沒有update的這個操作。項目組的人說這不會是oracle的bug吧,妥妥的臟讀啊,這如果真是oracle的bug,我覺得我可能要封神了,哈哈哈。冷靜下來還是仔細分析吧。仔細分析故障時的現象,當時是san交換機故障,數據庫無法訪問存儲,查看數據庫的alert日志,發現有大量LGWR all worker groups' for 2 secs字樣,這是lgwr進程寫重做日志的時候的等待,這么看來問題可能出現在update提交的時候。我們分解下事務提交的流程:
oracle事務提交時候流程:
1)為更新undo事務表生成一條更改向量
2)將更改向量復制到日志緩沖區
3)在undo端頭塊上應用更改向量
4)通知lgwr寫日志
5)lgwr 將 log buffer中的日志寫入redo
6)返回 Commit complete.
從事務提交流程看,應該是第5步夯住了,如果前四步已經做完了,盡管redo日志還沒有從redo buffer寫入到redolog,其它會話也是可以看到該事務在內存中的數據,而隨后db出現實例重啟,你可能發現提交的事務被
恢復了,而嚴格意義上說,這個事務也并沒有提交。
步驟4:場景復現
基于以上理解,我做了以下測試,可以看出,確實復現了當時的場景。


總結
至此,以上問題原因應該很清晰了,本文碰到的這個問題在oracle的一本書里有記載(忘記哪本書了,大概是個外國人寫的)。本次碰到的這個場景還是比較極端的,在生產環境應該不大容易遇到。




