SQLite 鎖機制學習總結 鎖狀態轉換及鎖機制實現代碼分析

一、操作系統API(以Windows爲例)

Windows可以對文件中的部分內容加共享鎖或排它鎖,並且加鎖區域可以在文件長度之外(超過文件尾的不實際存在的地方)。相關API函數爲:LockFile()、LockFileEx()和UnlockFile(),函數的詳細介紹可參考MSDN。

SQLite3.7版本對文件加鎖使用了LockFile()、LockFileEx()。因爲Win95, Win98, and WinME沒有LockFileEx(),使用LockFile()。之後的WinNT/2K/XP則使用LockFileEx()。

說明:

LockFile():加鎖區域不能重疊;

LockFileEx():可以對文件加共享鎖和排它鎖,共享鎖可以與共享鎖重疊,但排它鎖不能與任何鎖重疊;

二、鎖頁(加鎖區域)

在SQLite的頁(page)類型中有一種頁爲鎖頁(lock-byte page)。在SQLite數據文件中lock-byte page是一個單獨的頁,其位於數據庫文件的1073741824 and 1073742335,即1G的位置,佔512個字節;小於1G的數據庫文件則沒有此頁。

鎖頁是操作系統的加鎖區域,對其進行加鎖,以實現文件的併發訪問;SQLite本身並不使用此頁,不讀也不寫。

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位置開始的,不管實際存不存在。

1、加鎖區域PENDING_BYTE爲何設置爲0X4000 0000(1GB)?

前面說過,SQLite本身並不對加鎖區域進行讀寫,這512字節也不存儲實現的數據,如果數據庫文件實際數據大於1G,則浪費了512字節;而由於操作系統又可以對不存在的遙遠區域加鎖,所以把加鎖區域定在比較大的1G文件處(大部分數據庫文件都小於1G),這樣既不影響數據的存儲,也避免了512字節的空間損失。

2、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併發讀並沒有受到限制。

三、鎖類型

在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鎖。

四、SQLite鎖狀態轉換

SQLite採用粗放型的鎖。當一個連接要寫數據庫,所有其他的連接被鎖住,直到寫連接結束了它的事務。SQLite有一個加鎖表,來幫助不同的寫數據庫者能夠在最後一刻再加鎖,以保證最大的併發性。

SQLite使用鎖逐步上升機制,爲了寫數據庫,連接需要逐步地獲得排它鎖。對於5個不同的鎖狀態:未加鎖(UNLOCKED)、共享(SHARED)、保留(RESERVED)、未決(PENDING)和排它(EXCLUSIVE)。每個數據庫連接在同一時刻只能處於其中一個狀態。每種狀態(未加鎖狀態除外)都有一種鎖與之對應。鎖的狀態以及狀態的轉換如下圖所示:

SQLite鎖的狀態以及狀態的轉換

最初的狀態是未加鎖狀態,在此狀態下,連接還沒有存取數據庫。當連接到了一個數據庫,甚至已經用BEGIN開始了一個事務時,連接都還處於未加鎖狀態。

未加鎖狀態的下一個狀態是共享狀態。爲了能夠從數據庫中讀(不寫)數據,連接必須首先進入共享狀態,也就是首先要獲得一個共享鎖。多個連接可以同時獲得並保持共享鎖,也就是說多個連接可以同時從同一個數據庫中讀數據。但即使僅有一個共享鎖沒有釋放,也不允許任何連接寫數據庫。

如果一個連接想要寫數據庫,它必須首先獲得一個保留鎖。一個數據庫上同時只能有一個保留鎖。保留鎖可以與共享鎖共存,保留鎖是寫數據庫的第1階段。保留鎖即不阻止其它擁有共享鎖的連接繼續讀數據庫,也不阻止其它連接獲得新的共享鎖。

一旦一個連接獲得了保留鎖,它就可以開始處理數據庫修改操作了,儘管這些修改只能在緩衝區中進行,而不是實際地寫到磁盤。對讀出內容所做的修改保存在內存緩衝區中。

當連接想要提交修改(或事務)時,需要將保留鎖提升爲排它鎖。爲了得到排它鎖,還必須首先將保留鎖提升爲未決鎖。獲得未決鎖之後,其它連接就不能再獲得新的共享鎖了,但已經擁有共享鎖的連接仍然可以繼續正常讀數據庫。此時,擁有未決鎖的連接等待其它擁有共享鎖的連接完成工作並釋放其共享鎖。

一旦所有其它共享鎖都被釋放,擁有未決鎖的連接就可以將其鎖提升至排它鎖,此時就可以自由地對數據庫進行修改了。所有以前對緩衝區所做的修改都會被寫到數據庫文件。

五、鎖機制實現

實現程序位於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;

}

/* 以下三行代碼判斷當前所申請的鎖類型是否合法 */

/*

1、如果當前數據庫的鎖類型爲NO_LOCK,則所申請的鎖類型要爲SHARED_LOCK

2、所申請的鎖類型不能爲PENDING_LOCK,因爲其爲一種過渡鎖

3、如果所申請的鎖類型爲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 區域只是暫時的。

1、如果當前數據庫處於無鎖狀態,則首先要獲取共享鎖,這也是讀事務和寫事務在最初階段都要經歷的階段

2、如果前數據庫處於保留鎖狀態,而申請排它鎖進行寫庫,則要先獲取未決鎖。

此種情況是爲了阻止其它進程對此庫繼續申請共享鎖,以防止寫餓死。

*/

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區域加排它鎖,防止其它進程讀庫。

1、如果加鎖成功,則說明沒有其它進程在讀庫,因此可進行寫庫操作

2、如果加鎖失敗,則說明仍有其它進程正在進行讀操作,此時則無法獲取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

}

參考:

1、SQLite3源代碼,源碼版本 #define SQLITE_VERSION "3.7.14.1"

2、SQLite3源程序分析 作者:空轉

3、http://www.sqlite.com.cn

六、SQLite事務併發訪問及死鎖問題

再看一下這張圖:

從圖中可以看每個事務從開始到結束其鎖狀的變化。

1、對於一個讀事務會經過以下過程:

Ø UNLOCKED到PENDING;獲取PENDING鎖只是暫時的,獲取PENDING鎖是獲取SHARED鎖的第一步,因爲若有其它事務已獲取PENDING鎖,則此事務不能再獲取SHARED鎖了。

Ø 如果獲取PENDING鎖成功,則此事務可以繼續獲取SHARED鎖,並將之間獲取的PENDING釋放。

2、對於一個寫事務會以下過程

Ø 第一步和讀事務一樣,獲取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

COMMITPENDING

事務要提交,嘗試獲取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 IMMEDIATERESERVED

事務1開始時就獲取RESERVED

事務1

SELECT ... RESERVED

讀庫,已獲取RESERVED

事務1

INSERT ... RESERVED


事務2

BEGIN IMMEDIATE …

事務2開始時也嘗試獲取RESERVED鎖,但事務1已獲取RESERVED鎖未釋放,因此事務2開始失敗,返回SQLITE_BUSY,等待用戶重試

事務1

COMMIT EXCLUSIVE

事務要提交,成功獲取EXCLUSIVE鎖,寫庫完成後釋放鎖

事務2

BEGIN IMMEDIATERESERVED

事務1已完成,事務2開始成功

事務2

SELECT ... RESERVED


事務2

INSERT ... RESERVED


事務2

COMMIT EXCLUSIVE

寫入完成後釋放

因此,這樣就成功避免了死鎖的產生。

情況3以EXCLUSIVE的方式開始事務,即使其他連接以DEFERRED方式開啓也不會死鎖

事務

操作(鎖狀態)

說明

事務1

BEGIN EXCLUSIVEEXCLUSIVE

事務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方式開始。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章