1、操做系統API(以Windows爲例) sql
Windows能夠對文件中的部份內容加共享鎖或排它鎖,而且加鎖區域能夠在文件長度以外(超過文件尾的不實際存在的地方)。相關API函數爲:LockFile()、LockFileEx()和UnlockFile(),函數的詳細介紹可參考MSDN。 數據庫
SQLite3.7版本對文件加鎖使用了LockFile()、LockFileEx()。由於Win95, Win98, and WinME沒有LockFileEx(),使用LockFile()。以後的WinNT/2K/XP則使用LockFileEx()。 緩存
說明: 併發
LockFile():加鎖區域不能重疊; dom
LockFileEx():能夠對文件加共享鎖和排它鎖,共享鎖能夠與共享鎖重疊,但排它鎖不能與任何鎖重疊; 函數
2、鎖頁(加鎖區域) 性能
在SQLite的頁(page)類型中有一種頁爲鎖頁(lock-byte page)。在SQLite數據文件中lock-byte page是一個單獨的頁,其位於數據庫文件的1073741824 and 1073742335,即1G的位置,佔512個字節;小於1G的數據庫文件則沒有此頁。 this
鎖頁是操做系統的加鎖區域,對其進行加鎖,以實現文件的併發訪問;SQLite自己並不使用此頁,不讀也不寫。 spa
SQLite源碼os.h中對加鎖區域有以下定義: 操作系統
#define PENDING_BYTE sqlite3PendingByte
#define RESERVED_BYTE (PENDING_BYTE+1)
#define SHARED_FIRST (PENDING_BYTE+2)
#define SHARED_SIZE 510
SQLite在global.c中定義了全局變量sqlite3PendingByte:
int sqlite3PendingByte = 0x40000000;因此加鎖區域是從數據庫文件1G位置開始的,無論實際存不存在。
一、加鎖區域PENDING_BYTE爲什麼設置爲0X4000 0000(1GB)?
前面說過,SQLite自己並不對加鎖區域進行讀寫,這512字節也不存儲實現的數據,若是數據庫文件實際數據大於1G,則浪費了512字節;而因爲操做系統又能夠對不存在的遙遠區域加鎖,因此把加鎖區域定在比較大的1G文件處(大部分數據庫文件都小於1G),這樣既不影響數據的存儲,也避免了512字節的空間損失。
二、PENDING和RESERVED區域都爲一個字節,爲什麼SHARED爲510字節?
我我的以爲主要是因爲對於Windows來講,Win95, Win98, and WinME沒有LockFileEx(),而LockFile()加鎖區域不能重疊;在這種狀況下,爲了使兩個讀進程能夠同時訪問文件,對於SHARED LOCK則選擇一個SHARED_FIRST—SHARED_FIRST+ SHARED_SIZE範圍內的隨機數進行加鎖,以致於多個進程能夠同時讀數據庫文件,但有可能兩個進程取得同樣的lock byte,因此此種狀況對於Windows, SQLite的併發性就受到限制。但WinNT/2K/XP可使用LockFileEx()對文件重疊加共享鎖,所以在這種狀況下,SQLite併發讀並無受到限制。
3、鎖類型
在SQLite中爲了寫數據庫,鏈接須要逐步地得到排它鎖。SQLite有5個不一樣的鎖:未加鎖(NO_LOCK)、共享鎖(SHARED_LOCK)、保留鎖(RESERVED_LOCK)、未決鎖(PENDING_LOCK)和排它鎖(EXCLUSIVE_LOCK)。
SQLite在os.h中對鎖類型有以下定義:
#define NO_LOCK 0
#define SHARED_LOCK 1
#define RESERVED_LOCK 2
#define PENDING_LOCK 3
#define EXCLUSIVE_LOCK 4
隨着鎖級別的升高,其類型值也變大。
SQLite爲4種鎖定義了上述的3個區域,SQLite經過對數據庫文件的這3個區域加鎖來實現其鎖機制。
SHARED鎖:SHARED鎖意味着進程要讀(不寫)數據庫。一個數據庫上能夠同時有多個進程得到SHARED鎖,哪一個進程可以在SHARED_FIRST區域加共享鎖(使用LockFileEx()LockFileEx()函數),即得到了SHARED鎖。
RESERVED鎖: RESERVED鎖意味着進程將要對數據庫進行寫操做。一個數據庫上同時只能有一個進程擁有RESERVED鎖,因此哪一個進程可以在RESERVED_BYTE區域上加排它鎖(使用LockFile()或LockFileEx()函數),即得到了RESERVED鎖。RESERVED鎖能夠與SHARED鎖共存,並能夠繼續對數據庫加新的SHARED鎖。
爲何要用RESERVED鎖?
主要是出於併發性的考慮。因爲SQLite只有庫級排斥鎖(EXCLUSIVE LOCK),若是寫事務一開始就上EXCLUSIVE鎖,而後再進行實際的數據更新,寫磁盤操做,這會使得併發性大大下降。而SQLite一旦獲得數據庫的RESERVED鎖,就能夠對緩存中的數據進行修改,而與此同時,其它進程能夠繼續進行讀操做。直到真正須要寫磁盤時纔對數據庫加EXCLUSIVE 鎖。
PENDING鎖:PENDING LOCK意味着進程已經完成緩存中的數據修改,並想當即將更新寫入磁盤。它將等待此時已經存在的讀鎖事務完成,可是不容許對數據庫加新的SHARED LOCK(這與RESERVED LOCK相區別)。
一個數據庫上同時也只能有一個進程擁有PENDING鎖,因此哪一個進程可以在PENDING_BYTE區域上加排它鎖(使用LockFile()或LockFileEx()函數),即得到了PENDING鎖。
外部用戶並不能調用相應的加鎖函數加此種鎖,PENDING LOCK只是SQLite從RESERVED到EXCLUSIVE狀態的一箇中間鎖,用來防止寫餓死狀況。
爲何要有PENDING LOCK?
主要是爲了防止出現寫餓死的狀況。因爲寫事務先要獲取RESERVED LOCK,因此可能一直產生新的SHARED LOCK,使得寫事務發生餓死的狀況。
EXCLUSIVE鎖:雖然同時只能有一個進程擁有EXCLUSIVE鎖,但因爲在該進程擁有EXCLUSIVE鎖時不容許其餘進程擁有SHARED鎖,所以EXCLUSIVE鎖與SHARED鎖使用相同的文件區域。哪一個進程可以在SHARED_FIRST區域上加排它鎖(使用LockFile()或LockFileEx()函數,這樣操做系統就保證了不會有其餘SHARED鎖),即得到了PENDING鎖。
4、SQLite鎖狀態轉換
SQLite採用粗放型的鎖。當一個鏈接要寫數據庫,全部其餘的鏈接被鎖住,直到寫鏈接結束了它的事務。SQLite有一個加鎖表,來幫助不一樣的寫數據庫者可以在最後一刻再加鎖,以保證最大的併發性。
SQLite使用鎖逐步上升機制,爲了寫數據庫,鏈接須要逐步地得到排它鎖。對於5個不一樣的鎖狀態:未加鎖(UNLOCKED)、共享(SHARED)、保留(RESERVED)、未決(PENDING)和排它(EXCLUSIVE)。每一個數據庫鏈接在同一時刻只能處於其中一個狀態。每種狀態(未加鎖狀態除外)都有一種鎖與之對應。鎖的狀態以及狀態的轉換以下圖所示:
SQLite鎖的狀態以及狀態的轉換
最初的狀態是未加鎖狀態,在此狀態下,鏈接尚未存取數據庫。當鏈接到了一個數據庫,甚至已經用BEGIN開始了一個事務時,鏈接都還處於未加鎖狀態。
未加鎖狀態的下一個狀態是共享狀態。爲了可以從數據庫中讀(不寫)數據,鏈接必須首先進入共享狀態,也就是首先要得到一個共享鎖。多個鏈接能夠同時得到並保持共享鎖,也就是說多個鏈接能夠同時從同一個數據庫中讀數據。但即便僅有一個共享鎖沒有釋放,也不容許任何鏈接寫數據庫。
若是一個鏈接想要寫數據庫,它必須首先得到一個保留鎖。一個數據庫上同時只能有一個保留鎖。保留鎖能夠與共享鎖共存,保留鎖是寫數據庫的第1階段。保留鎖即不阻止其它擁有共享鎖的鏈接繼續讀數據庫,也不阻止其它鏈接得到新的共享鎖。
一旦一個鏈接得到了保留鎖,它就能夠開始處理數據庫修改操做了,儘管這些修改只能在緩衝區中進行,而不是實際地寫到磁盤。對讀出內容所作的修改保存在內存緩衝區中。
當鏈接想要提交修改(或事務)時,須要將保留鎖提高爲排它鎖。爲了獲得排它鎖,還必須首先將保留鎖提高爲未決鎖。得到未決鎖以後,其它鏈接就不能再得到新的共享鎖了,但已經擁有共享鎖的鏈接仍然能夠繼續正常讀數據庫。此時,擁有未決鎖的鏈接等待其它擁有共享鎖的鏈接完成工做並釋放其共享鎖。
一旦全部其它共享鎖都被釋放,擁有未決鎖的鏈接就能夠將其鎖提高至排它鎖,此時就能夠自由地對數據庫進行修改了。全部之前對緩衝區所作的修改都會被寫到數據庫文件。
5、鎖機制實現
實現程序位於os_win.c中:
/*
爲文件加鎖。
加鎖的類型locktype,爲如下之一:
(1) SHARED_LOCK
(2) RESERVED_LOCK
(3) EXCLUSIVE_LOCK
加鎖類型不能爲PENDING_LOCK,PENDING_LOCK爲內部自動過渡的一種鎖,外部用戶不該顯示的加此種鎖。代碼也作了判斷:assert( locktype!=PENDING_LOCK );
*/
static int winLock(sqlite3_file *id, int locktype){
int rc = SQLITE_OK; /* 返回值 */
int res = 1; /* Windows鎖函數操做的返回值*/
int newLocktype; /*在退出前將pFile->locktype設爲此值*/
int gotPendingLock = 0;/* 標識這次是否申請了一個PENDING lock this time */
winFile *pFile = (winFile*)id;
DWORD lastErrno = NO_ERROR;
assert( id!=0 );
OSTRACE(("LOCK %d %d was %d(%d)\n",
pFile->h, locktype, pFile->locktype, pFile->sharedLockByte));
/* 若是申請的鎖級別沒有當前文件所擁有的高,則直接返回*/
if( pFile->locktype>=locktype ){
return SQLITE_OK;
}
/* 如下三行代碼判斷當前所申請的鎖類型是否合法 */
/*
一、若是當前數據庫的鎖類型爲NO_LOCK,則所申請的鎖類型要爲SHARED_LOCK
二、所申請的鎖類型不能爲PENDING_LOCK,由於其爲一種過渡鎖
三、若是所申請的鎖類型爲RESERVED _LOCK,則當前數據庫的鎖類型要爲SHARED_LOCK
*/
assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK );
assert( locktype!=PENDING_LOCK );
assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK );
newLocktype = pFile->locktype;
/*
若是咱們申請 PENDING_LOCK 或 SHARED_LOCK,則須要對加鎖區域 PENDING_BYTE 進行加鎖 ;若是申請的是 SHARED_LOCK ,則鎖 PENDING_BYTE 區域只是暫時的。
一、若是當前數據庫處於無鎖狀態,則首先要獲取共享鎖,這也是讀事務和寫事務在最初階段都要經歷的階段
二、若是前數據庫處於保留鎖狀態,而申請排它鎖進行寫庫,則要先獲取未決鎖。
此種狀況是爲了阻止其它進程對此庫繼續申請共享鎖,以防止寫餓死。
*/
if( (pFile->locktype==NO_LOCK)
|| ( (locktype==EXCLUSIVE_LOCK)
&& (pFile->locktype==RESERVED_LOCK))
){
int cnt = 3;
/* 獲取未決鎖 */
while( cnt-->0 && (res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS,
PENDING_BYTE, 0, 1, 0))==0 ){
/* Try 3 times to get the pending lock. This is needed to work
** around problems caused by indexing and/or anti-virus software on
** Windows systems.
** If you are using this code as a model for alternative VFSes, do not
** copy this retry logic. It is a hack intended for Windows only.
*/
OSTRACE(("could not get a PENDING lock. cnt=%d\n", cnt));
if( cnt ) sqlite3_win32_sleep(1);
}
/* 設置gotPendingLock爲1,後面的程序根據此值可能會釋放PENDING鎖*/
gotPendingLock = res;
if( !res ){
lastErrno = osGetLastError();
}
}
/*
獲取 SHARED_LOCK
此時,事務應該持有 PENDING_LOCK(gotPendingLock == 1)。PENDING_LOCK 做爲事務從 NO_LOCK 到 SHARED_LOCK 的一個過渡,實際上此時鎖處於兩個狀態:PENDING和SHARED,直到後面釋放 PENDING_LOCK 後,才真正處於SHARED狀態。
*/
if( locktype==SHARED_LOCK && res ){
assert( pFile->locktype==NO_LOCK );
res = getReadLock(pFile);
if( res ){
newLocktype = SHARED_LOCK;
}else{
lastErrno = osGetLastError();
}
}
/*
獲取 RESERVED_LOCK
此時事務應持有 SHARED_LOCK,變化過程爲SHARED->RESERVED。
RESERVED鎖的做用就是爲了提升系統的併發性能。
*/
if( locktype==RESERVED_LOCK && res ){
assert( pFile->locktype==SHARED_LOCK );
res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, RESERVED_BYTE, 0, 1, 0);
if( res ){
newLocktype = RESERVED_LOCK;
}else{
lastErrno = osGetLastError();
}
}
/*
獲取 PENDING_LOCK
此時事務持有 RESERVED_LOCK,且事務申請 EXCLUSIVE_LOCK。
變化過程爲:RESERVED->PENDING。
PENDING狀態惟一的做用就是防止寫餓死。
讀事務不會執行此段代碼,但寫事務會執行該代碼。
執行該代碼後gotPendingLock設爲0,後面就不會釋放 RESERVED_LOCK 了。
*/
if( locktype==EXCLUSIVE_LOCK && res ){
/*
這裏沒有實際的加鎖操做,由於PENDING鎖前面已經加過了,
只是把鎖的狀態改成PENDING狀態
*/
newLocktype = PENDING_LOCK;
/*
設置gotPendingLock,後面就不會釋放PENDING鎖了,
至關於加了PENDING鎖,其實是在開始處加的PENDING鎖
*/
gotPendingLock = 0;
}
/*
獲取EXCLUSIVE_LOCK
此時事務應該持有 PENDING_LOCK,事務準備寫庫。
變化過程:PENDING->EXCLUSIVE
*/
if( locktype==EXCLUSIVE_LOCK && res ){
assert( pFile->locktype>=SHARED_LOCK );
/* 先解除該進程對數據庫加的共享鎖 */
res = unlockReadLock(pFile);
OSTRACE(("unreadlock = %d\n", res));
/*
對SHARED_FIRST區域加排它鎖,防止其它進程讀庫。
一、若是加鎖成功,則說明沒有其它進程在讀庫,所以可進行寫庫操做
二、若是加鎖失敗,則說明仍有其它進程正在進行讀操做,此時則沒法獲取EXCLUSIVE_LOCK,所以也沒法寫庫,事務仍持有PENDING_LOCK
*/
res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, SHARED_FIRST, 0,
SHARED_SIZE, 0);
if( res ){
newLocktype = EXCLUSIVE_LOCK;
}else{
lastErrno = osGetLastError();
OSTRACE(("error-code = %d\n", lastErrno));
/* 獲取排它鎖失敗,則繼續對文件加共享鎖 */
getReadLock(pFile);
}
}
/*
若是申請的是 SHARED_LOCK,此時須要釋放在前面得到的PENDING_LOCK。
鎖的變化爲:PENDING->SHARED
*/
if( gotPendingLock && locktype==SHARED_LOCK ){
winUnlockFile(&pFile->h, PENDING_BYTE, 0, 1, 0);
}
/* 改變文件的鎖狀態,返回適當的結果碼 */
if( res ){
rc = SQLITE_OK;
}else{
OSTRACE(("LOCK FAILED %d trying for %d but got %d\n", pFile->h,
locktype, newLocktype));
pFile->lastErrno = lastErrno;
rc = SQLITE_BUSY;
}
pFile->locktype = (u8)newLocktype;
return rc;
}
共享鎖獲取實現代碼以下:
static int getReadLock(winFile *pFile){
int res;
/*判斷操做系統類型*/
if( isNT() ){
/*爲WinNT/2K/XP系統,則直接使用LockFileEx對SHARED_SIZE進行加共享鎖*/
#if SQLITE_OS_WINCE
/*
** NOTE: Windows CE is handled differently here due its lack of the Win32
** API LockFileEx.
*/
res = winceLockFile(&pFile->h, SHARED_FIRST, 0, 1, 0);
#else
res = winLockFile(&pFile->h, SQLITE_LOCKFILEEX_FLAGS, SHARED_FIRST, 0,
SHARED_SIZE, 0);
#endif
}
#ifdef SQLITE_WIN32_HAS_ANSI
else{
/*
爲Win95, Win98, and WinME系統,則使用LockFile對SHARED_SIZE中的隨機字節進行加鎖(此鎖不能重疊)
*/
int lk;
sqlite3_randomness(sizeof(lk), &lk);
pFile->sharedLockByte = (short)((lk & 0x7fffffff)%(SHARED_SIZE - 1));
res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS,
SHARED_FIRST+pFile->sharedLockByte, 0, 1, 0);
}
#endif
if( res == 0 ){
pFile->lastErrno = osGetLastError();
/* No need to log a failure to lock */
}
return res;
}
static BOOL winLockFile(
LPHANDLE phFile,
DWORD flags,
DWORD offsetLow,
DWORD offsetHigh,
DWORD numBytesLow,
DWORD numBytesHigh
){
#if SQLITE_OS_WINCE
return winceLockFile(phFile, offsetLow, offsetHigh, numBytesLow, numBytesHigh);
#else
if( isNT() ){
OVERLAPPED ovlp;
memset(&ovlp, 0, sizeof(OVERLAPPED));
ovlp.Offset = offsetLow;
ovlp.OffsetHigh = offsetHigh;
return osLockFileEx(*phFile, flags, 0, numBytesLow, numBytesHigh, &ovlp);
}else{
return osLockFile(*phFile, offsetLow, offsetHigh, numBytesLow,
numBytesHigh);
}
#endif
}
參考:
一、SQLite3源代碼,源碼版本 #define SQLITE_VERSION "3.7.14.1"
二、SQLite3源程序分析 做者:空轉
6、SQLite事務併發訪問及死鎖問題
再看一下這張圖:
從圖中能夠看每一個事務從開始到結束其鎖狀的變化。
一、對於一個讀事務會通過如下過程:
Ø UNLOCKED到PENDING;獲取PENDING鎖只是暫時的,獲取PENDING鎖是獲取SHARED鎖的第一步,由於如有其它事務已獲取PENDING鎖,則此事務不能再獲取SHARED鎖了。
Ø 若是獲取PENDING鎖成功,則此事務能夠繼續獲取SHARED鎖,並將之間獲取的PENDING釋放。
二、對於一個寫事務會如下過程
Ø 第一步和讀事務同樣,獲取SHARED鎖。
Ø 獲取RESERVED鎖,一旦事務要進行寫操做,首先就要獲取此鎖。
Ø 獲取EXCLUSIVE鎖,實際上此時要先獲取PENDING鎖,以阻止其它事務繼續獲取SHARED鎖(由於前面說過獲取PENDING鎖是獲取SHARED鎖的第一步),進而防止寫餓死。
Ø 獲取PENDING鎖後,才真去獲取EXCLUSIVE鎖;若是獲取EXCLUSIVE鎖成功,則事務就能夠進行寫磁盤操做了。
對於上述兩種事務鎖狀態變化狀況可參考前節的鎖機制實現代碼分析部分。
對於一個SQLite的寫事務來講,理想狀況下是各類鎖正常獲取,直到事務提交;但實際並不徹底這樣,SQLite也可能出現死鎖。
好比:
狀況1:以默認方式(DEFERRED)開始事務:
事務 |
操做(鎖狀態) |
說明 |
事務1 |
BEGIN … (UNLOCKED) |
事務1開始時處於無鎖狀態 |
事務1 |
SELECT ... (SHARED) |
讀庫,獲取SHARED鎖 |
事務1 |
INSERT ... (RESERVED) |
準備寫庫,獲取RESERVED鎖 |
事務2 |
BEGIN .. (UNLOCKED) |
事務2開始時也處於無鎖狀態 |
事務2 |
SELECT ... (SHARED) |
事務1此時仍爲RESERVED鎖狀態,因此事務2依然能夠獲取SHARED鎖 |
事務1 |
COMMIT (PENDING) |
事務1 要提交,嘗試獲取EXCLUSIVE鎖,先獲取了PENDING鎖;在去獲取EXCLUSIVE鎖時,發現還有SHARED鎖未釋放,則獲取失敗,返回SQLITE_BUSY |
事務2 |
INSERT ... |
準備寫庫,嘗試獲取RESERVED鎖,但事務1已有RESERVED鎖未釋放,則獲取失敗,返回SQLITE_BUSY |
此種狀況則發生的死鎖:
事務1因事務2獲取的SHARED鎖未釋放而獲取EXCLUSIVE鎖失敗(前面說過SHARED鎖和EXCLUSIVE鎖共用同一加鎖區域);
事務2因事務1已獲取了RESERVED鎖未釋放而獲取RESERVED鎖失敗;
爲了不上述死鎖的產生,則要爲應用程序選擇合適的事務類型。SQLite有三種不一樣的事務類型(在BEGIN命令中指定:BEGIN [ DEFERRED | IMMEDIATE | EXCLUSIVE ] TRANSACTION)。
BEGIN [ DEFERRED] TRANSACTION:默認狀況下是這樣的。事務開始時並不獲取任何鎖,直到它須要鎖的時候才加鎖,並且BEGIN語句自己也不會作什麼事情,—它開始處於UNLOCK狀態;若是僅僅用BEGIN開始一個事務,那麼事務就是DEFERRED的,同時它不會獲取任何鎖,當對數據庫進行第一次讀操做時,它會獲取SHARED LOCK;一樣,當進行第一次寫操做時,它會獲取RESERVED LOCK。
BEGIN [IMMEDIATE] TRANSACTION:事務開始時會試着獲取RESERVED LOCK,若是成功,則事務處於RESERVED鎖狀態,以保證後續沒有別的鏈接能夠寫數據庫,可是,別的鏈接仍能夠對數據庫進行讀操做; RESERVED LOCK會阻止其它的鏈接以BEGIN IMMEDIATE或者BEGIN EXCLUSIVE命令開始事務,SQLite會返回SQLITE_BUSY錯誤;但能夠以BEGIN DEFERRED命令開始事務。事務開始成功後,就能夠對數據庫進行修改操做;當COMMIT時,若是返回SQLITE_BUSY錯誤,這意味着還有其它的讀事務沒有完成,得等它們執行完後才能提交事務。
BEGIN [EXCLUSIVE] TRANSACTION:事務開始時會試着獲取EXCLUSIVE LOCK。這與IMMEDIATE相似,可是一旦成功,EXCLUSIVE事務保證沒有其它的鏈接(其它事務讀庫都不行,由於獲取不到SHARED LOCK),因此此事務就可對數據庫進行讀寫操做了。
狀況2:以IMMEDIATE方式開始事務
事務 |
操做(鎖狀態) |
說明 |
事務1 |
BEGIN IMMEDIATE(RESERVED) |
事務1開始時就獲取RESERVED鎖 |
事務1 |
SELECT ... (RESERVED) |
讀庫,已獲取RESERVED鎖 |
事務1 |
INSERT ... (RESERVED) |
|
事務2 |
BEGIN IMMEDIATE … |
事務2開始時也嘗試獲取RESERVED鎖,但事務1已獲取RESERVED鎖未釋放,所以事務2開始失敗,返回SQLITE_BUSY,等待用戶重試 |
事務1 |
COMMIT (EXCLUSIVE) |
事務1 要提交,成功獲取EXCLUSIVE鎖,寫庫完成後釋放鎖 |
事務2 |
BEGIN IMMEDIATE(RESERVED) |
事務1已完成,事務2開始成功 |
事務2 |
SELECT ... (RESERVED) |
|
事務2 |
INSERT ... (RESERVED) |
|
事務2 |
COMMIT (EXCLUSIVE) |
寫入完成後釋放 |
所以,這樣就成功避免了死鎖的產生。
狀況3:以EXCLUSIVE的方式開始事務,即便其餘鏈接以DEFERRED方式開啓也不會死鎖
事務 |
操做(鎖狀態) |
說明 |
事務1 |
BEGIN EXCLUSIVE (EXCLUSIVE) |
事務1開始時就獲取EXCLUSIVE鎖 其它事務則不能獲取SHARED鎖,所以也不能讀庫 |
事務1 |
SELECT ... (EXCLUSIVE) |
|
事務1 |
INSERT ... (EXCLUSIVE) |
|
事務2 |
BEGIN (UNLOCKED) |
|
事務2 |
SELECT ... |
嘗試獲取SHARED鎖,但事務1已獲取EXCLUSIVE鎖未釋放,則返回SQLITE_BUSY,等待用戶重試 |
事務1 |
COMMIT (EXCLUSIVE) |
寫庫完成後釋放鎖 |
事務2 |
SELECT ... (SHARED) |
|
事務2 |
INSERT ... (RESERVED) |
|
事務2 |
COMMIT (EXCLUSIVE) |
寫入完成後釋放 |
以EXCLUSIVE的方式開始事務要求更爲苛刻,直接獲取EXCLUSIVE鎖的難度比較大;爲了不EXCLUSIVE狀態長期阻塞其餘請求,最好的方式仍是讓全部寫事務都以IMMEDIATE方式開始。