您好,登錄后才能下訂單哦!
簡介
保證數據的一致性是數據庫的一個最最基本的功能,那數據庫在機器down機或者出現其他意外的情況下是如何去保證數據庫的數據的一致性的呢?數據庫本身主要依靠undolog和redolog兩種日志文件去保持數據的一致性,本文將圍繞undolog進行介紹。如何利用undolog去實現數據庫的一致性。
數據庫架構簡介
要介紹數據庫一致性的實現機制,自然少不了要介紹下數據庫的整體架構,這里畫一個簡圖來介紹下數據庫的架構。(因為數據庫的架構不是本文重點,所以這里就畫個簡圖,想要詳細了解數據庫架構的,可以再查閱其他文章。)
將數據庫簡化,主要分這么幾塊:
§ 查詢處理器,主要負責針對于查詢sql的解析、執行計劃的選擇等等。
§ 事務管理器,事務是數據庫操作的最小單位,事務管理器主要針對于分配事務id等等管理工作
§ 日志管理器
§ 恢復管理器
§ 緩沖管理器,這個大家也清楚,數據庫中的寫操作都是在緩沖器中完成,然后再flush到硬盤上。
§ 硬盤數據,日志無論是數據庫的數據、還是日志文件,都是最終要寫到硬盤上做持久化存儲的。
下面就針對于上面說的這幾塊組成部分,來描述下數據庫是如何做災難恢復的。這一篇主要還是講undolog,下一篇再講redolog。
undo日志簡介
undo日志,顧名思義,就是撤銷日志,也就是說,這個日志里面記錄了相關的撤銷操作。通過剛才的數據庫架構簡圖,我們也可以看到,針對于寫數據等處理主要還是在內存中進行,既然在內存中進行,那就會出現由于機器宕機導致的丟數據問題。那數據庫是如何通過undo日志去保證數據的一致性的呢?
要描述這個問題,我們需要先定義幾種操作。假設我們現在要做這樣一件事情,需要把某條數據X從數據庫中讀出來,然后再去改變他的值,改為Y,然后再寫回去。好,這樣一個操作,數據庫可能要走這樣幾個流程,首先會看緩沖區里有沒有,假設有則直接將數據返回,我們稱這樣一個過程為Read(X),假設緩沖區里沒有,那就需要先從硬盤讀到緩沖區,然后再返回給用戶,那我們定義從硬盤讀到緩沖區的過程為Input(X),也就是說,如果緩沖區里沒有,那數據庫要先經過一個Read(X),然后再經過一個Input(X),然后再經過一個Read(X)。那修改時也一樣,數據庫要修改緩沖區的內容,那這個操作我們成為Write(Y).還要經過一個從內存刷到磁盤的過程,這個過程我們成為output(Y)。好,有了這幾個定義,我們就挨個從這幾個過程去分析,如果數據庫再這中間某個過程掛了,如何去保證數據的一致性。
在介紹前,先簡要說下undolog日志的格式,undolog日志的格式是這樣的 <T,A,X>,T就代表事務ID,A就代表某一行的某一列,X就代表原先的值。也就是說這條日志就代表T這個事務時,A原先的值為X,對,undolog僅僅記錄原先的值,他并不關心你把它改成了多少,他關心的就是原先是多少,因為將來只是做撤銷工作。除了這個之外,undolog中還記錄start ,意為開啟一個事務,commit,意為提交一個事務。大體我們就可以先抽象成這幾個。
那比如我們要做上面那個問題,即讀取一個值再修改(假設不在緩沖區中),那就要經過以下幾步:
見表格:
編號 | 操作 | undolog |
1 | – | start |
2 | Read(X) | – |
3 | Input(X) | – |
4 | Read(X) | – |
5 | Write(Y) | – |
6 | – | <T,A,X> |
7 | flush undolog | |
8 | Output(Y) | – |
9 | … | … |
10 | commit | – |
11 | – | end |
10 | flush undolog |
我們看上面這個表格,下面我就解釋下這個表格。首先,我們需要明確的一點是,無論是操作數據庫中的數據,還是日志,都是先在內存中操作,然后flush到硬盤上。這一點是毫無疑問的。
前4步應該都容易理解,一開始在undolog中需要記錄一個start的標志位,然后2、3、4步都是讀取數據庫的內容,第5步往內存中寫,將X的值改為Y,然后第6步undolog中會記錄下事務T中A的原先值是X,那到了第7步呢?是到底是應該先將undolog進行flush,還是應該output后再flush呢?
我們做個假設,假設output后再進行log的flush,如果恰好就在output后,數據庫down機了,那這樣的結果顯而易見,日志文件里并沒有記載undolog(因為沒有flush到硬盤),無法重做所以就會導致數據不一致。所以要在output后進行undolog的flush是不可取的。
那我們看下上面這個順序的意義,假設在第6-7步之間宕機了,也就就是在還未flush undolog時已宕機,那不會影響數據的一致性,因為本來數據就沒有寫到硬盤。如果是在第7-8步間宕機,雖然數據庫的數據沒有寫到硬盤,但log已經flush了,那這時會通過flush后的log重做一遍,因為系統不知道這個log做過了沒有,不過即使是重做一遍也不影響最終的數據一致性,也只是將原先的數據又重新寫了一遍而已,就是從X寫為X,這個不影響數據庫一致性,undolog是冪等的,也就是做幾次的結果都是一樣。所以上面這個順序才是合理的。
通過undolog進行數據恢復
既然有了undolog,那我們看下數據庫是如何通過undolog進行數據恢復的。這時上面架構簡圖里的恢復管理器就起了作用了,恢復管理器會掃描undolog,找出沒有end掉的start,因為從上面的順序中我們可以看出,“end”記錄flush到log中是在事務提交后才flush的,所以,只要是有了end記錄了,就說明了這個事務本身已經結束了,數據一致性已經可以保證了。所以恢復管理器這時是掃描沒有end配位的start,然后從start開始往后,根據undolog中記錄的先前的值進行重做。不過根據我們剛才這個模型會發現,在恢復管理器進行重做時,是不能有其他的寫入的,也就是現在的寫入應該是夯住狀態的。而且還有個問題,恢復管理器需要從頭開始對于undolog進行掃描,其實這是不必須的,完全可以有個checkpoint(代表在這之前的數據已經可以保證數據一致性了)的,那恢復管理器只需要找到最后一個checkpoint,然后從checkpoint往后做就可以了。
針對于以上這兩個問題,我們下面進行討論。
在上面介紹的模型中,恢復管理器必須要通過全篇掃描整個undolog進行日志恢復,這樣做顯然是沒有太大必要的,因為系統中斷肯定是在最后幾個事務受到影響,前面的事務應該已經完成commit或者rollback了,不會出現abort的情況,那我們如何知道哪些事務受到了影響呢,如果我們知道了哪一些事務受到了影響,那我們就可以不用全篇進行掃描,而僅僅掃描很小的一部分就可以了。下面就介紹下,數據庫如何知道哪些事務受到了影響,數據庫為了得到這個目的,引入了檢查點(checkpoint)這個概念。
checkpoint,即檢查點。在undolog中寫入檢查點,表示在checkpoint前的事務都已經完成commit或者rollback了,也就是檢查點前面的事務已經不存在數據一致性的問題了。那這個checkpoint如何去實現呢。其實實現的機制很簡單,就是周期性的往undolog里面寫入。當然這個寫入肯定不是隨隨便便的往里寫,在往里寫的時候,肯定要檢查前面的事務是否完成。
這個時候就會帶來一個問題,因為數據庫是一直在運行的,也就是事務是在不斷啟動的,同時可能有n個事務已經處于開始狀態。而在檢查點往里寫的時候,可能又有新的事務啟動了,如果讓檢查點一直等到沒有新的事務啟動而且前面所有的事務又都提交過了估計很難,那基本檢查點就不用往里寫了。所以,在這種情況下,只能是在檢查點往里寫的時候,停止接受新事務,等待已啟動的事務提交完畢,然后檢查點寫入完畢。然后繼續接受新事務。類似于這樣:例如,現在有T1 T2兩個事務,則undolog中寫入:
undolog |
start T1 |
<T1,A,x> |
start T2 |
<T2,B,y> |
這時到了檢查點的周期,要往里寫入檢查點了,就得等到T1,T2全部提交完畢,然后寫入檢查點chkpoint。也就是如果現在有一個T3要開啟,是無法開啟的。系統處于夯住狀態。寫入完后,開啟T3,日志記錄如下:
undolog |
start T1 |
<T1,A,x> |
start T2 |
<T2,B,y> |
end T1 |
end T2 |
chkpoint |
start T3 |
這時候,如果系統掛掉了,故障恢復管理器會從undolog的尾部向前進行掃描,掃描到checkpoint后,就不會往前掃描了,因為前面的事務都已經提交過了,不存在數據一致性問題。所以只需要從checkpoint開始重做即可。
這樣固然是好,省掉了需要undolog從頭開始掃描的麻煩,但是這樣做的缺點也很明顯,那就是在寫入checkpoint的過程中,系統是出于夯住狀態的,所有的寫入都要暫停。那能否有一種更好的方法既可以寫入checkpoint又不需要系統暫停呢,必須的,當然有,這就是下面要講的非靜態檢查點。
非靜態檢查點是相對于靜態檢查點而來的,上文中所提到的就屬于靜態檢查點,因為在檢查點寫入的同時,系統是不能寫入的。而非靜態檢查點的引入,就是要解決這個問題。
非靜態檢查點的策略是在寫入chkpoint的同時,會記錄下當前活躍的事務。比如,當前狀態下,T1和T2都是活躍狀態,那么undolog中會被寫入start checkpoint(T1,T2),這時整體系統仍然是正常寫入的,也就是說在這條log寫入后,仍然可以繼續開啟其他事務。當T1,T2完成后,會寫入end checkpoint的記錄。例如如下記錄:
undolog |
start T1 |
<T1,A,x> |
start T2 |
<T2,B,y> |
start checkpoint(T1,T2) |
start T3 |
end T1 |
end T2 |
end chkpoint |
start checkpoint(T3) |
end T3 |
end chkpoint |
上面這個日志記錄就是,在T1,T2開始后,undolog中寫入了start checkpoint(T1,T2)的檢查點,而這時仍然是可以接受其他事務的開始的,這時有了T3事務的開啟。
通過這種機制,可以有效避免在檢查點寫入時需要停掉服務的弊端,但現在問題又來了,這樣寫檢查點固然是好,但恢復管理器如何通過這樣的undolog去進行數據恢復操作呢?因為,如果檢查點是靜止的,那找到checkpoint后,就不必再往前找了,而現在不一樣了,因為找到endcheckpoint后,前面仍可能有未完成的事務,那這時數據恢復是如何恢復的呢?
在這種情況下,數據庫宕機后,恢復管理器仍然會從尾往前進行掃描undolog,如果遇到了“end chkpoint”,這時并不代表checkpoint前所有的事務都已經提交了,但我們可以知道,所有未提交的事務都是在上一個startcheckpoint之后,所以會繼續往前找,一直找到startcheckpoint,找到startcheckpoint后,比如是startcheckpoint(T1,T2),因為先前已經找到了endchkpoint,所以T1,T2這兩個事務已經可以保證數據一致性了,需要重做的就是在startchecpoint(T1,T2)到end chkpoint間的這一些非T1,T2事務,這些是需要重做的,所以要把這些進行重做。
還有另外一種情況,就是恢復管理器在掃描時,先遇到了startcheckpoint(T1,T2)的日志,在這種情況下,我們首先知道了T1,T2或許是未完成的事務,那這時需要在start checkpoint之后找到是否有某個事務的end語句,如果有,說明這個事務是完成了,如果沒有,就說明沒有完成,那就要從checkpoint再往后尋找,找到這個事務的start,然后從start之后往后重做。說得比較羅嗦,我們上個例子來說明下這種情況。
例如,數據庫宕機后,開始掃描undolog,得到以下片段:
undolog |
start T1 |
<T1,A,15> |
start T2 |
<T2,B,39> |
start checkpoint(T1,T2) |
start T3 |
<T3,C,40> |
end T1 |
<T3,C,50> |
<T3,C,50> |
這時,恢復管理器拿到這個片段后進行掃描,在遇到end chkpoint前遇到了start checkpoint(T1,T2),這說明了,T1,T2是可能未完成事務的,而且在這之前還遇到了T3的start,沒有end T3,也沒有任何T3的檢查點的開始,這說明了T3一定是未完成事務的,所以T3一定是要重做的。先前為什么說T1,T2是可能未完成事務的呢?因為遇到了start checkpoint(T1,T2),沒有遇到end chkpoint,并不代表T1和T2就一定是未完成的,可能有一個已經commit過了,因為兩個都沒有commit,所以才導致了沒有end chkpoint,所以這時找start下面的日志,發現了“end T1”,說明了T1的事務是已經完成了的。那只需要找T2的開啟然后開始重做就可以了,然后就通過start checkpoint(T1,T2)再往上找,找到了start T2,然后開始重做T2,也就是這個日志里,T2和T3是需要重做的,然后重做掉。(注:剛才先說了做T3,然后有說了重做T2,并不代表真正的順序就是這樣,實際上恢復管理器是先分析出需要重做的事務,然后一塊做掉的。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。