標籤:
說到事務一定會提到ACID,所謂事務的原子性,一致性,隔離性和持久性。對於一個資料庫而言,通常通過並發控制和故障恢複手段來保證事務在正常和異常情況下的ACID特性。sqlite也不例外,雖然簡單,依然有自己的並發控制和故障恢複機制。Sqlite學習筆記(五)&&SQLite封鎖機制 已經講了一些鎖機制的原理,本文將會詳細介紹一個事務從開始,到執行,最後到提交所經曆的過程,其中會穿插講一些sqlite中鎖管理,緩衝管理和日誌管理的機制,同時會介紹在異常情況下(軟硬體故障,比如程式異常crash,主機掉電等),sqlite如何將資料庫恢複到事務之前的狀態。本文大量參考了sqlite的官方文檔,結合自己的理解,希望能把這個過程說清楚。
1.事務提交
1.1 開啟一個事務
在向資料庫檔案寫資料前,sqlite首先需要訪問sqlite_master表擷取中繼資料資訊,用來對SQL語句進行語義分析,判斷語句的合法性。從資料庫讀資料第一步,是對資料庫檔案上一個Shared Lock。Shared Lock允許多個事務同時讀一個資料庫檔案,但是Shared Lock會阻止寫事務向資料庫檔案寫入資料。
1.2 讀資料
擷取Shared Lock後,我們可以從資料庫檔案中讀取資料了。我們假設緩衝中沒有我們的page,因此需要通過讀檔案讀取我們需要的page。這裡說明下,sqlite的資料庫檔案實質是有一個個大小相同的page組成,預設情況下,一個page大小為1024B。通常情況下,我們需要讀取若干個page,並把這些page緩衝在應用本地的cache中,這樣下次訪問就不需要再次從檔案中讀取。這裡我們假設需要讀取3個page,用綠色塊表示。
1.3 擷取Reserved Lock
在向資料庫寫資料之前,Sqlite需要擷取一個Reserved Lock,Reserved Lock與Shared Lock類似,同時允許其他事務讀取資料庫。Reserved Lock與Shared Lock相容,但與ReservedLock互斥,即同一個時刻只允許有一個ReservedLock。持有Reserved Lock表示事務準備要修改資料檔案了,由於還沒有真正修改檔案,因此允許其他事務繼續進行讀操作,但不允許其他事務進行寫資料庫操作。
1.4 建立記錄檔
在sqlite中,有兩種日誌技術,影子分頁技術和WAL(write ahead log)技術。影子分頁技術是sqlite預設採用的方式,後面的討論都是基於這種假設。在操作資料檔案之前,sqlite首先建立一個記錄檔,並將準備要修改的page的內容寫入日誌,通過這種方式保留了恢複事務的所有原始資訊。無論是資料庫檔案,還是記錄檔,最基本的操作單位都是page。
1.5 修改資料
前面提到,sqlite修改資料前,先將page讀到cache中,因此修改會首先修改cache中的資料。由於每個串連都有自己獨立的page cache,因此寫事務修改自己page cache中的資料,不會影響其他事務,其他事務依然會讀到原始的page資料,不會導致髒讀。中紅色表示修改塊,可以看到,只有使用者自身cache的page變成了紅色。
1.6 刷記錄檔
修改完成後,首先將記錄檔寫入磁碟。這個過程非常重要,只有通過刷盤操作(fsync)將日誌持久化,才能在掉電的情況下,通過日誌恢複資料頁。同時,這個動作也非常耗時。
1.7 擷取ExclusiveLock
現在我們需要將之前對page cache的修改寫入資料庫檔案,達到持久化目的。在這個動作之前,首先需要持有Exclusive Lock,擷取該鎖實際包含兩個步驟,首先持有一個Pending Lock,然後再持有Exclusive Lock。Pending Lock允許持有讀鎖事務繼續進行讀操作,但不允許新的讀事務進來。由於新的讀事務被阻止,則將讀事務數目限制在一定的範圍,而已有的讀事務遲早都會執行完,寫事務最終可以擷取到ExclusiveLock,通過這種方式避免寫事務餓死的情況。
1.8 將修改寫入資料檔案並刷盤
一旦持有了ExclusiveLock,則此時sqlite中只有一個事務,沒有其他讀事務去讀檔案。因此,這時候向資料檔案中寫資料是安全的。為了保證寫入動作真正落入磁碟,還需要進行刷盤動作。與刷日誌一樣,將資料檔案修改刷盤動作也是為了保證掉電情況下,更新依然可以持久化,同樣這個操作也很耗時。其中紅色塊表示修改塊,此時使用者進程空間,OS buffer,以及DISK都已經修改。
1.9 刪除記錄檔
進行這步時,記錄檔和資料檔案修改都已經固化到磁碟。如果在1.8步之前,發生掉電,由於記錄檔已經安全落盤,因此可以將資料庫恢複到事務開始前的狀態。由於資料檔案修改已經固化,我們可以將記錄檔刪除。通過記錄檔的存在與否,判斷我們是需要將交易回復還是提交。由於刪檔案也是一個比較耗時的動作,sqlite對此進行了最佳化,通過參數選項,可以選擇將日誌全部初始化0,或是直接將檔案截斷,達到提高效能的目的。
1.10 釋放Exclusive Lock
最後一步是釋放ExclusiveLock,這樣其他事務才有機會讀、寫資料檔案。這裡有一個問題,每個串連都有自己的page cache,如果page cache中的內容已經被改了,並寫入到了檔案中,那麼其它事務如何感知,將自己本地的old-page清理,重新從檔案中讀?sqlite通過一個計數器來控制,這個計數器存在資料庫檔案的第一個page中。每次資料檔案修改時,這個值也會同時自增。事務開始時,會讀取計數器,在讀取page 時,會再次檢查計數器是否發生變化,如果發生變化,說明有事務提交,則將本地的cache全部清空,重新從資料庫檔案中擷取。
2.交易回復
正常情況下,通過上述的事務提交流程,就可以保證事務的ACID特性。但是事務在執行過程中發生異常呢,這時候就需要通過交易回復來將資料庫恢複到事務開始前的狀態。下面假設一種情況,來介紹復原流程。
2.1 發生故障
假設在1.8之前,寫資料庫檔案時,發生了掉電故障。當故障恢複後,情形可能如右圖所示,只有部分頁寫入了磁碟,甚至有一個頁可能唯寫入了一部分。由於執行到這個步驟時,日誌已經安全落盤,因此可以藉助日誌進行恢複。
2.2 熱日誌
任何一個串連在操作資料庫之前,會首先判斷是否有熱日誌存在,因為有熱日誌存在意味著可能需要故障恢複。所謂熱日誌,是指需要事務提交過程中發生了故障,需要利用日誌恢複。
2.3 復原未完成的操作
在利用日誌進行恢複前,首先持有ExclusiveLock,這樣避免多個串連同時進行故障恢複,持有ExclusiveLock後,才可以開始修改資料庫檔案。sqlite從記錄檔中讀取原始的資料頁,然後將資料頁寫回到資料檔案中。由於記錄檔頭部記錄了事務開始時資料檔案的大小,sqlite利用這個資訊來講資料檔案進行截斷到原來的大小,保持檔案大小恢複到事務開始時的水平。
2.4 刪除記錄檔
當所有記錄檔中的資料頁都已經拷貝到資料檔案中後,進行一次刷盤操作,確保修改持久化,這時候記錄檔可以被刪除了。恢複完成後,將Exclusive Lock 降級到Shared Lock。這個過程完成後,資料庫完成恢複。由於整個過程都是sqlite自動完成,使用者完全無感知。對於使用者而言,任何時候使用sqlite操作資料檔案都是安全的,即使在發生了異常的情況下。
參考文檔
https://www.sqlite.org/atomiccommit.html
SQLite學習筆記(七)&&交易處理