SQLite學習手冊(鎖和併發控制)

1、概述:

    在SQLite中,鎖和併發控制機制都是由pager_module模塊負責處理的,如ACID(Atomic, Consistent, Isolated, and Durable)。在含有數據修改的事務中,該模塊將確保或者全部的數據修改所有提交,或者所有回滾。與此同時,該模塊還提供了一些磁盤文件的內存Cache功能。
    事實上,pager_module模塊並不關心數據庫存儲的細節,如B-Tree、編碼方式、索引等,它只是將其視爲由統一大小(一般爲1024字節)的數據塊構成的單一文件,其中每一個塊被稱爲一個頁(page)。在該模塊中頁的起始編號爲1,即第一個頁的索引值是1,其後的頁編號以此類推。
    
2、文件鎖:

    在SQLite的當前版本中,主要提供瞭如下五種方式的文件鎖狀態。
    1). UNLOCKED:
    文件沒有持有任何鎖,即當前數據庫不存在任何讀或寫的操做。其它的進程能夠在該數據庫上執行任意的讀寫操做。此狀態爲缺省狀態。
    2). SHARED:
    在此狀態下,該數據庫能夠被讀取可是不能被寫入。在同一時刻能夠有任意數量的進程在同一個數據庫上持有共享鎖,所以讀操做是併發的。換句話說,只要有一個或多個共享鎖處於活動狀態,就再也不容許有數據庫文件寫入的操做存在。
    3). RESERVED:
    假如某個進程在未來的某一時刻打算在當前的數據庫中執行寫操做,然而此時只是從數據庫中讀取數據,那麼咱們就能夠簡單的理解爲數據庫文件此時已經擁有了保留鎖。當保留鎖處於活動狀態時,該數據庫只能有一個或多個共享鎖存在,即同一數據庫的同一時刻只能存在一個保留鎖和多個共享鎖。在Oracle中此類鎖被稱之爲預寫鎖,不一樣的是Oracle中鎖的粒度能夠細化到表甚至到行,所以該種鎖在Oracle中對併發的影響程序不像SQLite中這樣大。
    4). PENDING:
    PENDING鎖的意思是說,某個進程正打算在該數據庫上執行寫操做,然而此時該數據庫中卻存在不少共享鎖(讀操做),那麼該寫操做就必須處於等待狀態,即等待全部共享鎖消失爲止,與此同時,新的讀操做將再也不被容許,以防止寫鎖飢餓的現象發生。在此等待期間,該數據庫文件的鎖狀態爲PENDING,在等到全部共享鎖消失之後,PENDING鎖狀態的數據庫文件將在獲取排他鎖以後進入EXCLUSIVE狀態。
    5). EXCLUSIVE:
    在執行寫操做以前,該進程必須先獲取該數據庫的排他鎖。然而一旦擁有了排他鎖,任何其它鎖類型都不能與之共存。所以,爲了最大化併發效率,SQLite將會最小化排他鎖被持有的時間總量。
    
    最後須要說明的是,和其它關係型數據庫相比,如MySQL、Oracle等,SQLite數據庫中全部的數據都存儲在同一文件中,與此同時,它卻僅僅提供了粗粒度的文件鎖,所以,SQLite在併發性和伸縮性等方面和其它關係型數據庫仍是沒法比擬的。因而可知,SQLite有其自身的適用場景,就如在本系列開篇中所說,它和其它關係型數據庫之間的互換性仍是很是有限的。

3、回滾日誌:

    當一個進程要改變數據庫文件的時候,它首先將未改變以前的內容記錄到回滾日誌文件中。若是SQLite中的某一事務正在試圖修改多個數據庫中的數據,那麼此時每個數據庫都將生成一個屬於本身的回滾日誌文件,用於分別記錄屬於本身的數據改變,與此同時還要生成一個用於協調多個數據庫操做的主數據庫日誌文件,在主數據庫日誌文件中將包含各個數據庫回滾日誌文件的文件名,在每一個回滾日誌文件中也一樣包含了主數據庫日誌文件的文件名信息。然而對於無需主數據庫日誌文件的回滾日誌文件,其中也會保留主數據庫日誌文件的信息,只是此時該信息的值爲空。
    咱們能夠將回滾日誌視爲"HOT"日誌文件,由於它的存在就是爲了恢復數據庫的一致性狀態。當某一進程正在更新數據庫時,應用程序或OS忽然崩潰,這樣更新操做就不能順利完成。所以咱們能夠說"HOT"日誌只有在異常條件下才會生成,若是一切都很是順利的話,該文件將永遠不會存在。

4、數據寫入:

    若是某一進程要想在數據庫上執行寫操做,那麼必須先獲取共享鎖,在共享鎖獲取以後再獲取保留鎖。由於保留鎖則預示着在未來某一時刻該進程將會執行寫操做,因此在同一時刻只有一個進程能夠持有一把保留鎖,可是其它進程能夠繼續持有共享鎖以完成數據讀取的操做。若是要執行寫操做的進程不能獲取保留鎖,那麼這將說明另外一進程已經獲取了保留鎖。在此種狀況下,寫操做將失敗,並當即返回SQLITE_BUSY錯誤。在成功獲取保留鎖以後,該寫進程將建立回滾日誌。
    在對任何數據做出改變以前,寫進程會將待修改頁中的原有內容先行寫入回滾日誌文件中,然而,這些數據發生變化的頁起初並不會直接寫入磁盤文件,而是保留在內存中,這樣其它進程就能夠繼續讀取該數據庫中的數據了。
    或者是由於內存中的cache已滿,或者是應用程序已經提交了事務,最終,寫進程將數據更新到數據庫文件中。然而在此以前,寫進程必須確保沒有其它的進程正在讀取數據庫,同時回滾日誌中的數據確實被物理的寫入到磁盤文件中,其步驟以下:
    1). 確保全部的回滾日誌數據被物理的寫入磁盤文件,以便在出現系統崩潰時能夠將數據庫恢復到一致的狀態。
    2). 獲取PENDING鎖,再獲取排他鎖,若是此時其它的進程仍然持有共享鎖,寫入線程將不得不被掛起並等待直到那些共享鎖消失以後,才能進而獲得排他鎖。
    3). 將內存中持有的修改頁寫出到原有的磁盤文件中。
    若是寫入到數據庫文件的緣由是由於cache已滿,那麼寫入進程將不會馬上提交,而是繼續對其它頁進行修改。可是在接下來的修改被寫入到數據庫文件以前,回滾日誌必須被再一次寫到磁盤中。還要注意的是,寫入進程獲取到的排他鎖必須被一直持有,直到全部的改變被提交時爲止。這也意味着,從數據第一次被刷新到磁盤文件開始,直到事務被提交以前,其它的進程不能訪問該數據庫。
    當寫入進程準備提交時,將遵循如下步驟:
    4). 獲取排他鎖,同時確保全部內存中的變化數據都被寫入到磁盤文件中。
    5). 將全部數據庫文件的變化數據物理的寫入到磁盤中。
    6). 刪除日誌文件。若是在刪除以前出現系統故障,進程在下一次打開該數據庫時仍將基於該HOT日誌進行恢復操做。所以只有在成功刪除日誌文件以後,咱們才能夠認爲該事務成功完成。
    7). 從數據庫文件中刪除全部的排他鎖和PENDING鎖。
    一旦PENDING鎖被釋放,其它的進程就能夠開始再次讀取數據庫了。
    若是一個事務中包含多個數據庫的修改,那麼它的提交邏輯將更爲複雜,見以下步驟:
    4). 確保每一個數據庫文件都已經持有了排他鎖和一個有效的日誌文件。
    5). 建立主數據庫日誌文件,同時將每一個數據庫的回滾日誌文件的文件名寫入到該主數據庫日誌文件中。
    6). 再將主數據庫日誌文件的文件名分別寫入到每一個數據庫回滾日誌文件的指定位置中。
    7). 將全部的數據庫變化持久化到數據庫磁盤文件中。
    8). 刪除主日誌文件,若是在刪除以前出現系統故障,進程在下一次打開該數據庫時仍將基於該HOT日誌進行恢復操做。所以只有在成功刪除主日誌文件以後,咱們才能夠認爲該事務成功完成。
    9). 刪除每一個數據庫各自的日誌文件。
    10).從全部數據庫中刪除掉排他鎖和PENDING鎖。
    
    最後須要說明的是,在SQLite2中,若是多個進程正在從數據庫中讀取數據,也就是說該數據庫始終都有讀操做發生,即在每一時刻該數據庫都持有至少一把共享鎖,這樣將會致使沒有任何進程能夠執行寫操做,由於在數據庫持有讀鎖的時候是沒法獲取寫鎖的,咱們將這種情形稱爲"寫飢餓"。在SQLite3中,經過使用PENDING鎖則有效的避免了"寫飢餓"情形的發生。當某一進程持有PENDING鎖時,已經存在的讀操做能夠繼續進行,直到其正常結束,可是新的讀操做將不會再被SQLite接受,因此在已有的讀操做所有結束後,持有PENDING鎖的進程就能夠被激活並試圖進一步獲取排他鎖以完成數據的修改操做。
    
5、SQL級別的事務控制:

    SQLite3在實現上確實針對鎖和併發控制作出了一些精巧的變化,特別是對於事務這一SQL語言級別的特徵。在缺省狀況下,SQLite3會將全部的SQL操做置於antocommit模式下,這樣全部針對數據庫的修改操做都會在SQL命令執行結束後被自動提交。在SQLite中,SQL命令"BEGIN TRANSACTION"用於顯式的聲明一個事務,即其後的SQL語句在執行後都不會自動提交,而是須要等到SQL命令"COMMIT"或"ROLLBACK"被執行時,才考慮提交仍是回滾。由此能夠推斷出,在BEGIN命令被執行後並無當即得到任何類型的鎖,而是在執行第一個SELECT語句時才獲得一個共享鎖,或者是在執行第一個DML語句時纔得到一個保留鎖。至於排它鎖,只有在數據從內存寫入磁盤時開始,直到事務提交或回滾以前才能持有排它鎖。
    若是多個SQL命令在同一個時刻同一個數據庫鏈接中被執行,autocommit將會被延遲執行,直到最後一個命令完成。好比,若是一個SELECT語句正在被執行,在這個命令執行期間,須要返回全部檢索出來的行記錄,若是此時處理結果集的線程由於業務邏輯的須要被暫時掛起並處於等待狀態,而其它的線程此時或許正在該鏈接上對該數據庫執行INSERT、UPDATE或DELETE命令,那麼全部這些命令做出的數據修改都必須等到SELECT檢索結束後才能被提交。
相關文章
相關標籤/搜索