Sqlite學習筆記(五)&&SQLite封鎖機制


概述

     SQLite雖然是一個輕量的嵌入式數據庫,但這並不影響它支持事務。所謂支持事務,即需要在並發環境下,保持事務的ACID特性。事務的原子性,隔離性都需要通過並發控制來保證。那么Sqlite的並發控制是怎樣的,如何實現,在這里跟大家分享下我的理解。

     SQLite是一個文件數據庫,所有的數據都在一個db文件中,對於wal模式,還包含wal索引文件和wal日志文件。SQlite支持庫級並發,即允許多個讀事務同時運行,同一時刻最多只有一個寫事務,讀寫沖突,相對於傳統的DBMS支持表級,行級甚至MVCC,SQLite的庫級並發確實顯得比較寒磣。但是鎖粒度越細,意味着維護鎖的成本越高,系統也會越復雜,因此SQLite的封鎖機制要簡單很多,對資源的消耗也非常少。SQLite 3.7版本后,對並發控制做了優化,推出了WAL日志模式,可以實現讀寫並發,但同一個時刻仍然只能有一個寫事務。由於SQLite的實現方式,SQLite只支持兩種隔離級別,串行化和讀未提交。讀未提交,就是讀全程不上鎖;串行化在事務開啟時上讀鎖,上鎖和釋放鎖同樣遵守兩階段鎖協議,在事務提交或回滾時才釋放鎖。

文件鎖

     要說清楚SQLite鎖實現機制,首先要了解文件鎖,因為SQLite所有鎖實現都是基於文件鎖。對於Linux系統,文件鎖主要包含兩類,協同鎖和強制鎖,協同鎖類似於互斥量,需要參與者都遵守游戲規則,在操作文件前,都先上鎖,而強制鎖由OS內核強制實行。協同鎖根據鎖粒度分為文件級別和范圍級別。鎖文件是最簡單的對文件加鎖的方法,每個需要加鎖的數據文件都有一個鎖文件(lock file)。當鎖文件存在時,就認為該數據文件已經被加鎖,別的進程不應該訪問。當鎖不存在,進程就可以創建一個鎖文件,然后訪問相應的數據文件。只要創建鎖的過程是原子的,就能保證某一時刻只有一個進程擁有該鎖,這種方法保證某一時刻只有一個進程訪問文件。文件鎖的弊端顯而易見,並發粒度太低。范圍鎖相對於文件鎖,可以鎖文件的一部分內容,並且有讀鎖和寫鎖。對於同一部分內容,讀鎖可以共存,讀鎖和寫鎖互斥。POSIX標准提供接口fcntl()來實現。

鎖類型   

       SQLite中的鎖正是利用了范圍鎖來實現並發控制的目的。SQLite中主要包含了4種鎖:共享鎖(SHARED_LOCK)、保留鎖(RESERVED_LOCK)、未決鎖(PENDING_LOCK)和排它鎖(EXCLUSIVE_LOCK),這4種鎖定義了3個區域,其中共享鎖和排它鎖占用文件相同的區域。具體而言,SQLite定義了文件的以下區域為鎖文件區域,由於fcntl可以對不存在的文件區域加鎖,因此 PENDING_BYTE定位在區域1G的地方,即使DB文件沒這么大也不影響。三種類型的鎖,分別在1G,1G+1,1G+2的偏移處,之所以SHARED_SIZE長度是510,原因在於windows環境下,LockFile()加鎖區域不能重疊(Linux沒有這種問題),對於同一個字節上鎖會影響並發,因此設置了一個范圍,對SHARED_FIRST—SHARED_FIRST+ SHARED_SIZE范圍內的隨機數進行加鎖,這樣可以減少沖突,保證高效的讀取文件。具體鎖類別和說明參見表1 

鎖類別

字節范圍

說明

PENDING_BYTE

0x40000000

一種過渡鎖,讀事務獲取讀鎖,寫事務獲取寫鎖前,都需要獲取該鎖。

RESERVED_BYTE

0x40000001

表示線程要開始寫操作,某一時刻只能有一個RESERVED Lock,但是RESERVED鎖和SHARED鎖可以共存,而且可以對數據庫加新的SHARED鎖。

SHARED_LOCK

0x40000002-0x40000200

共享鎖,開啟事務時,都需要獲取該鎖

EXCLUSIVE_LOCK

0x40000002-0x40000200

排它鎖

                                                       表1

      從各個鎖的作用來看,不免會疑問,為啥要加上RESERVED_LOCK和PENDING_LOCK兩種類型,直接通過共享鎖和排它鎖不就可以達到讀讀共享,讀寫互斥的目的了嗎。這里引入這Reserved鎖的目的是為了提高並發。由於SQLite只有庫級排斥鎖(EXCLUSIVE LOCK),如果寫事務一開始就上EXCLUSIVE鎖,然后再進行實際的數據更新,寫磁盤操作,這會使得並發性大大降低。而SQLite一旦得到數據庫的RESERVED鎖,就可以對緩存中的數據進行修改,而與此同時,其它進程可以繼續進行讀操作。直到真正需要寫磁盤時才對數據庫加EXCLUSIVE鎖。Pending鎖的作用主要是為了防止寫餓死的情況,寫事務獲取Pending鎖后,新的讀事務無法再進來,然后再加EXCLUSIVE鎖,這樣寫事務獲取鎖的幾率大大提高,讀寫事務的流程如下表2,狀態變遷圖如圖1。

類型

操作

鎖信息

說明

讀事務

begin

 

不持有鎖

select c1 from user where id=1

Lock: Pending(Read)

Lock:Shared(Read)

Unlock:Pending

獲取Shared讀鎖前,需要先獲取Pending共享鎖,

通過這種方式與寫事務互斥。

commit

UnLock:Shared

 

寫事務

begin

 

 

Update c1=c1+1 where id=1

Lock: Pending(Read)

Lock:Shared

Unlock:Pending

Lock:Reserved(Write)

先獲取Shared讀鎖,然后獲取Reserved的排它鎖,阻止其它寫事務

commit

Lock:Pending(Write)

Lock:Exclusive(Write)

Unlock: Pending

Unlock: Exclusive(Write)

獲取Pending的排它鎖,阻止新的讀事務,最后上排它鎖,阻止所有讀事務,讀寫不能並發

Pending鎖方式好處是,減少寫餓死的幾率。

                                                       表2

 

                                         圖1

Wal鎖類型

      引入WAL機制后,SQLite開始支持讀寫並發,並且引入了WAL日志文件鎖。WAL日志鎖實質是鎖wal-index文件的區域,根據不同的鎖類型,將wal-index文件的不同區域划定義成不同的鎖,主要有讀鎖,寫鎖,檢查點鎖,具體如表3,4。WAL模式下,最新的數據位於日志文件中,無論是讀事務還是寫事務都需要持有WAL_READ_LOCK的讀鎖,因為它們都需要獲取最新的事務點。因此,做檢查點時,可以通過對WAL_READ_LOCK位置(124-127)上鎖,來確定檢查點需要等待還是停止推進。同時我們也可以看到,對於DB文件,讀寫事務都只需要對DB文件上讀鎖,對於WAL日志文件,WAL_READ_LOCK和WAL_WRITE_LOCK位於不同的位置,讀寫相互不影響,所以讀寫不互斥。 

鎖類別

字節范圍

說明

讀事務(WAL)

begin

 

 

select c1 from user where id=1

DB文件:

Lock: Pending(Read)

Lock:Shared

Unlock:Pending

WAL文件:

Lock:WAL_READ_LOCK(Read)

除了獲取DB文件鎖,還需要獲取WAL鎖,得到最新提交事務的位點。

若有事務再作檢查點,需要重試多次。

commit

Unlock:WAL_READ_LOCK

Unlock:Shared

 

寫事務(WAL)

begin

 

 

Update c1=c1+1 where id=1

DB文件:

Lock: Pending-Read

Lock:Shared(Read)

Unlock:Pending

WAL文件:

Lock:WAL_READ_LOCK(Read)

Lock:WAL_WRITE_LOCK(Write)

通過

EXCLUSIVE-WRITE-LOCK控制寫寫並發

由於不操作DB文件,因此不存在讀寫沖突,讀寫可以並發。

commit

WAL文件:

Lock:SHARED-READ-LOCK

Unlock:WAL_READ_LOCK(Read)

Unlock: WAL_WRITE_LOCK(Write)

 

DB文件:

Unlock:Shared

獲取SHARED-READ-LOCK目的是為了獲取最新提交日志的位點

檢查點

操作

(WAL)

 

 

WAL文件:

Lock:WAL_CKPT_LOCK(write)

Lock:WAL_READ_LOCK(write)

UnLock:WAL_READ_LOCK

UnLock:WAL_CKPT_LOCK

EXCLUSIVE-CKPT-LOCK
保證只有一個寫事務做檢查點;

WAL_READ_LOCK阻止讀寫事務。

                                                  表3 

鎖類別

字節范圍

說明

WAL_WRITE_LOCK

120

寫鎖位置

WAL_CKPT_LOCK

121

檢查點鎖位置

WAL_RECOVER_LOCK

122

故障恢復鎖位置

WAL_READ_LOCK

123

讀鎖(表示不需要wal文件)

 

124-127

讀鎖(每個位置,對應一個鎖)

做檢查點時,逐一對每個位置上寫鎖,若上鎖失敗表示對應位置上的讀事務沒有結束,根據檢查點策略確定是等待(FULL),還是停止推進(PASSIVE)。

                                                 表4   

調試

     SQLite通過幾個宏定義可以打印語句執行的鎖信息,方便大家了解語句執行中加了哪些鎖,什么時候加的,什么時候釋放的,以及如何處理鎖沖突。具體的宏包括SQLITE_LOCK_TRACE,SQLITE_FORCE_OS_TRACE,和SQLITE_DEBUG,具體可以在代碼中查看宏定義的注釋。

gcc sqlite3.c -g -lpthread -ldl -fPIC -shared -DSQLITE_TEST -DSQLITE_DEBUG -DSQLITE_LOCK_TRACE -DSQLITE_FORCE_OS_TRACE -o libsqlite3.so

參考文檔

http://my.oschina.net/u/587236/blog/129022

http://www.cnblogs.com/hustcat/archive/2009/03/01/1400757.html

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM