概述
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
圖1wal鎖型別
鎖類別位元組範圍 說明
讀事務(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:shared-read-lock
unlock:shared
寫事務(wal)
begin
update c1=c1+1 where id=1
db檔案:
lock: pending-read
lock:shared(read)
unlock:pending
wal檔案:
lock:shared-read-lock[0]
lock:exclusive-write-lock 通過
exclusive-write-lock控制寫寫併發
由於不操作db檔案,因此不存在讀寫衝突,讀寫可以併發。
commit
wal檔案:
lock:shared-read-lock
unlock:shared-read-lock
unlock: exclusive-write-lock
db檔案:
unlock:shared
獲取shared-read-lock目的是為了獲取最新提交日誌的位點
檢查點操作 (wal)
wal檔案:
lock:exclusive-ckpt-lock
lock:exclusive-write-lock
unlock:exclusive-write-lock
unlock:exclusive-ckpt-lock
exclusive-ckpt-lock
保證只有乙個寫事務做檢查點;
exclusive-write-lock阻止讀事務。
表3除錯
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參考文件
python自帶sqlite 使用SQLite
python就內建了sqlite3,所以,在python中使用sqlite,不需要安裝任何東西,直接使用。在使用sqlite前,我們先要搞清楚幾個概念 表是資料庫中存放關係資料的集合,乙個資料庫裡面通常都包含多個表,比如學生的表,班級的表,學校的表,等等。表和表之間通過外來鍵關聯。要操作關聯式資料庫...
SQLite學習筆記
官方站點 從 的download頁面獲取 download.html sqlite amalgamation 3 6 22.zip是sqlite的windows下原始碼檔案 sqlite 3 6 22.zip sqlitedll 3 6 22.zip 在sqlite中,表示式 a between b...
SQLite學習筆記
官方站點 從 的download頁面獲取 download.html sqlite amalgamation 3 6 22.zip是sqlite的windows下原始碼檔案 sqlite 3 6 22.zip sqlitedll 3 6 22.zip 在sqlite中,表示式 a between b...