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
?
?
轉載于:https://www.cnblogs.com/cchust/p/4761814.html
總結
以上是生活随笔為你收集整理的Sqlite学习笔记(五)SQLite封锁机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Qt 5.9.6 下载及安装
- 下一篇: queryDsl引入Mysql内置函数示