lightning mdb 源代碼分析(5)-事務控制

本博文系列前面已經探討了LMDB的系統架構、MMAP映射、B-Tree操作等部分,本文將嘗試描述LMDB中的事務控制的實現。

事務的基本特徵:

事務是恢復和併發控制的基本單位。它是一個操作序列,這些操作要麼都執行,要麼都不執行,它是一個不可分割的工作單位。

事務是數據庫維護數據一致性的單位,在每個事務結束時,都能保持數據一致性。

事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱爲ACID特性

原子性(atomicity)。一個事務是一個不可分割的工作單位,事務中包括的諸操作要麼都做,要麼都不做。

一致性(consistency)。事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。

隔離性(isolation)。一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。

持久性(durability)。持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。

LMDB中的實現基本思路:

Atom(A):LMDB中通過txn數據結構和cursor數據結構的控制,通過將髒頁列表放入dirtylist中,當txn進行提交時再一次性統一刷新到磁盤

中或者abort時都不提交保證事務要不全成功、要不全失敗。對於長事務,若頁面spill到磁盤,因爲COW技術,這些頁面未與整棵B-Tree的root

page產生關聯,因此後續的事務還是不能訪問到這些頁面,同樣保證了事務的原子性。

 

其數據就是一致的,不存在因爲多線程同時寫數據導致數據產生錯誤的情況。

Isolation(I):事務隔離通過鎖控制(MUTEX),LMDB支持的鎖互斥是進程級別/線程級別,支持的隔離方式爲鎖表支持,讀讀之間不鎖,寫等待讀完成之後開始,

讀等待寫完成後開始

Duration(D):LMDB中,沒有使用WAL、undo/redo log等技術來保證系統崩潰時數據庫的可用性,其保證數據持續可用的技術是COW技術和只有一線程寫技術。

假如LMDB或者系統崩潰時,只有讀操作,那麼數據本來就沒有發生變化,因此數據將不可能遭到破壞。假如崩潰時,有一個線程在進行寫操作,則只需要判斷最後的

頁面號與成功提交到數據庫中的頁面號是否一致,若不一致則說明寫操作沒有完成,則最後一個事務寫失敗,數據在最後一個成功的頁面前的是正確的,後續的屬於

崩潰事務的,不能用,這樣就保證了數據只要序列化到磁盤則一定可用,要不其就是還沒有遵循ACI原則序列化到磁盤

順便說一句,因爲MMAP技術、只一個寫線程的實現方案,所以數據庫進行備份時特別簡單,只要定期在線熱備整個數據庫即可完成。同時恢復也將比較快。當然由於

其使用了重用舊頁技術,LMDB在恢復時只能恢復到最新狀態,不能恢復到任意時刻。

實現方法:

LMDB支持嵌套事務,不期望在子事務完成之前父事務有任何讀寫操作,這樣的話可以避免父子事務之間的數據不一致。

LMDB不支持跨線程事務,一個事務只能屬於一個線程,一個線程在任一時刻只能持有一個事務。

mdb_txn_begin:

     開啓一個事務,根據是否傳入父事務判斷是否爲子事務,根據傳入參數判斷是否爲只讀事務。嵌套事務支持只支持一個子事務,且子事務爲寫事務父事務也必須爲寫事務,

而且數據庫不能爲mmap可寫方式。事務開啓流程:分配內存,設置變量,若爲子事務,設置父子相關關聯變量並shadow父親所有cursor以減少IO讀取。否則調用renew0完成

最終的事務開啓工作。

mdb_txn_abort:

    放棄一個事務,有子事務則先放棄子事務,然後調用reset0真正執行結束操作。
mdb_txn_commit:

    提交一個事務,有子事務則先提交子事務,若爲只讀事務,則關閉所有打開的數據庫句柄並保持打開狀態,然後放棄事務即可,若爲可寫事務,確定事務狀態是否正確,若爲error

狀態,不可以提交,若不是則根據是否存在父事務進行處理,沒有父事務則首先更新數據庫的root節點,然後保存可重用空間到freedb以便空間重用,並釋放midl空間之後,進行

頁面刷新,同步相關環境變量之後釋放內存,最後釋放寫鎖,至此沒有父事務情況提交完成。若有父事務,則其進行將midl列表與父事務的midl合併,cursor同樣合併到父事務中進行

最終關閉,將dirtylist合併到父事務中,相關合並和本事務的變量內存釋放完畢之後,子事務提交成功,即子事務主要完成內存釋放,其他動作如磁盤刷新等都合併至父事務中一次性完成。

mdb_txn_reset:

    放棄一個事務,但是保留句柄,僅對只讀事務有效,同樣調用reset0進行真正事務結束操作。
mdb_txn_reset0 :

    放棄事務的公共代碼.首先關閉事務中打開的數據庫句柄。若是隻讀事務,設置事務相關變量即可,若爲可寫事務,需要關閉所有遊標,然後釋放midl空間,最後釋放寫鎖。至此事務

關閉完畢。
mdb_txn_renew:

    重用一個只讀事務句柄,避免一次內存分配,檢查是否有嚴重錯誤,若有失敗,沒有的話調用renew0完成。
mdb_txn_renew0:

    renew0是renew和begin的公共代碼。若是寫事務,申請進程間互斥鎖,若是讀事務,首先檢查本線程是否已經有讀事務,有不支持返回錯誤,沒有的話,開始申請讀表互斥鎖,

成功後將線程id記錄到讀表裏面,然後立刻釋放讀表鎖。然後再次確認線程中確有事務。事務(讀寫)申請成功後,將env的meta頁面根據txnid進行切換,輪流使用。

最後再次設定些變量後通知調用者申請成功。
mdb_txn_env:

   返回事務關聯的env對象

    上文解釋了LMDB實現事務控制的方式和主要接口方法的基本流程,若實現類似關係型數據庫的細粒度事務,則需要更細粒度的鎖以及複雜的頁面等待隊列機制等以保證行鎖或表鎖

的正確性並最終實現事務控制機制,且在數據庫應用時有可能陷入死鎖狀態,而在LMDB當中,讀寫鎖分開,且進程崩潰時,系統會釋放相關內核變量,從而保證要不進程正常,

鎖成功釋放,要不進程崩潰,系統釋放鎖,因此數據庫永遠不會陷入死鎖狀態,不過若事務在等待寫鎖,有可能等待較長時間。

    希望各位能積極批評指正以及轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章