聊聊數據庫的IO丟失問題
昨天晚上一個朋友打電話咨詢一個Oracle數據庫無法啟動的問題,是因為之前出現的異常宕機引發的。這是一個因為數據庫IO丟失引發的數據庫不一致問題,Oracle在數據庫啟動的 時候發現了一些比較嚴重的不一致問題,就會無法打開數據庫。在數據庫原理中,數據庫通過Write Ahead Log(WAL)機制來確保數據庫在實例宕機之后不會出現數據的不一致問題,不會丟失已經提交過的事務。從基礎原理上,似乎數據庫能夠自己保證自己的一致性,為什么還會出現類似的問題呢?
實際上這種設計是有一個前提的,那就是每個已經寫盤的IO,都真實地物理落盤了。這是數據庫的數據永遠保持一致性的前提,如果這個前提出現了漏洞,那么這個理論上的永遠一致性就不存在了。
事實上,數據庫的IO鏈路很長,操作系統、RAID卡、HBA卡、SAN交換機緩沖區、集中式存儲機頭、集中式存儲的寫緩沖、磁盤的寫緩沖、磁盤,等等。我可能還沒有羅列完整,因為在不同的環境中,這個鏈路還會略微不同。在很多層面,IO都會被優化,因此真實的IO落盤并非和我們想象的一樣。如果在云環境中,這個IO路徑就更為復雜了。
當IO在這些環節中的某個緩沖中丟失了,那么數據庫的底層就會丟失IO了,這個IO丟失會引發一系列的數據不一致。比如一個8K的數據塊,前半部分已經寫盤,但是后半部分的寫IO丟失了,這樣就會出現“塊斷裂”,一個數據塊的數據不一致了。
MySQL等數據庫使用DOUBLE WRITE BUFFER來解決這個問題,PG則采用FULL PAGE WRITE LOG的機制。Oracle則比較粗獷,完全不管這個問題,讓底層存儲系統來確保寫IO的原子性。究其原因,Oracle自從出生開始,就是和高端硬件關聯的,和MySQL/PG這些草根的設計思路完全不同。
數據庫面對復雜的底層環境,所以無法確保其基礎理論的實現,這個可能會出乎一些朋友的意料。Michael Stonebraker老爺子要搞DBOS,其目的是為數據庫提供一個完全以數據庫的設計理念為基礎的底層環境。這個理想很宏大,實現起來恐怕也是困難重重的。因為這些違背數據庫設計理念的設計都是為了優化。一個通用的DBOS可能無法適應不同的數據庫產品的通用優化需求。DBOS作為一個數據庫SAAS服務的基礎平臺是可行的,成為一個通用的數據庫底座任重道遠。
當底層IO出現丟失的時候,Oracle處置起來是最為麻煩的,我想很多Oracle的老DBA也都因此賺了不少錢,昨晚我那個朋友就因為幫人打開了一個出現ORA-600[2662]的數據庫而賺了1萬塊錢的外快。這是因為Oracle的控制文件、REDO、UNDO、數據文件一旦因為IO丟失而出現不一致會引發數據庫無法打開的問題。
ORA-01113、ORA-600[2662]、ORA-600[3020]、ORA-600 [4000]、ORA-600[4193]、ORA-600[4194]等錯誤的出現往往就與這些有關。20年前我在ORACLEFANS網站上也發過不少處置類似問題的文章,在微信公眾號里我也寫過一篇《如何強制打開無法啟動的Oracle數據庫》,里面簡單地介紹了一些處理方法。
如果出現ORA-600[2662],整個處理過程會麻煩一些,因為數據庫啟動的時候發現某個數據文件的SCN已經高于數據庫的當前SCN。早期我們可以通過adjust_scn事件來往前推進數據庫的當前SCN,從而解決這個問題。Oracle 11.2.0.2.6以后,adjust_scn等待事件被廢棄了,如果遇到這種情況,用oradebug 修改內存中的CURRENT SCN也可以起作用。從Oracle 12c開始廢棄了這種做法,Oracle又提供了一個EVENT 21307096來解決這個問題(詳情請參考《Force Open Database after applying Patch 21307096 (Doc ID 2674196.1)》)。
實際上Oracle數據庫IO丟失在SYSTEM表空間中才是最麻煩的,因為在SYSTEM表空間中存在bootstrap objects,還有一個system rollback segment。
如果這些對象丟失了IO,那么就需要使用BBED這樣的工具去修復才能避開問題,強行打開數據庫。強行打開了丟失IO的數據庫之后,大部分數據可以順利導出,如果某些表上還是存在壞塊,需要通過跳過壞塊的技術來導出表中的數據。一般來說這個數據庫已經不能作為生產庫使用了,導出數據后重建數據庫是最好的做法。
一般來說這些處置手段都是作為最后的處置手段,如果數據庫存在備份,而且備份中丟失的數據在合理的范圍內,通過備份恢復數據庫可能是更好的做法。遇到這樣問題的客戶往往是比較慷慨的,因此DBA掌握這些技術,還是有價值的。