SQLite3源碼學習(25) Pager模塊之事務鎖的實現2
在上一篇文章中介紹了SQLite怎么用Linux中的記錄鎖來實現每一種類型的事務鎖。但這只適合多進程間的互斥,不適合多線程,在Linux中每一個進程只能擁有一把鎖,也就是說一個進程里的多個線程共用一把鎖,這時會出現一個線程擁有共享鎖,另一個線程再獲取獨占鎖時并不會出現排斥,僅僅是把當前進程的鎖改為獨占鎖,還有當一個線程占有鎖的時候可能被另一個線程釋放等等,這就破壞了數據庫中多事務的隔離性。所以SQLite在進程內部自己維護著每個事務對文件鎖的擁有情況。
1.inode
為了管理進程內部的鎖,首先需要對每個文件做唯一標識,SQLite使用文件的inode作為唯一標識。要注意不能用文件描述符,因為Linux中存在一個文件是另一個文件的硬連接或軟連接(可以理解成類似快捷方式的東西),它們雖然有不同文件名,打開的文件描述符不同,實際上對應著同一個文件。
每打開一個事務連接,SQLite會通過fstat()函數尋找對應的文件的inode,并關聯到連接句柄中。如果是第一次打開文件,則把對應的inode插到全局鏈表inodeList中。
static int findInodeInfo(
unixFile *pFile, /* Unix file with file desc used in the key */
unixInodeInfo **ppInode /* Return the unixInodeInfo object here */
){
int rc; /* System call return code */
int fd; /* The file descriptor for pFile */
struct unixFileId fileId; /* Lookup key for the unixInodeInfo */
struct stat statbuf; /* Low-level file information */
unixInodeInfo *pInode = 0; /* Candidate unixInodeInfo object */
assert( unixMutexHeld() );
/* Get low-level information about the file that we can used to
** create a unique name for the file.
*/
fd = pFile->h;
rc = osFstat(fd, &statbuf);
if( rc!=0 ){
storeLastErrno(pFile, errno);
return SQLITE_IOERR;
}
memset(&fileId, 0, sizeof(fileId));
fileId.dev = statbuf.st_dev;
#if OS_VXWORKS
fileId.pId = pFile->pId;
#else
fileId.ino = (u64)statbuf.st_ino;
#endif
pInode = inodeList;
while( pInode && memcmp(&fileId, &pInode->fileId, sizeof(fileId)) ){
pInode = pInode->pNext;
}
if( pInode==0 ){
pInode = sqlite3_malloc64( sizeof(*pInode) );
if( pInode==0 ){
return SQLITE_NOMEM_BKPT;
}
memset(pInode, 0, sizeof(*pInode));
memcpy(&pInode->fileId, &fileId, sizeof(fileId));
pInode->nRef = 1;
pInode->pNext = inodeList;
pInode->pPrev = 0;
if( inodeList ) inodeList->pPrev = pInode;
inodeList = pInode;
}else{
pInode->nRef++;
}
*ppInode = pInode;
return SQLITE_OK;
}
在SQLite中用unixInodeInfo對象來保存每個inode相關信息,每個文件只能有一個inode,這里記錄了該文件在進程內鎖的分配情況,每一個事務連接都有一個pInode指針來指向unixInodeInfo對象。
下圖描述了一個進程內的3個事務打開同一個數據庫文件,其中有2個線程,其中一個線程包含2個事務。
在unixFile對象里,h為每個連接的句柄,eFileLock為事務當前鎖的類型。在unixInodeInfo對象里,nRef為文件對應的連接數,eFileLock為所有連接中等級最高的鎖,nShared為當前鎖的個數。
2.鎖的實現
在sqlite中事務鎖的接口為:
int sqlite3OsLock(sqlite3_file *id, int lockType)
int sqlite3OsUnlock(sqlite3_file *id, int lockType)
在Linux中對應的接口是:
static int unixLock(sqlite3_file *id, int eFileLock)
static int unixUnlock(sqlite3_file *id, int eFileLock)
下面就來講解SQLite是如何在進程內部管理鎖的分配,每個鎖的對應狀態的宏定義如下,數字越大等級越高
#define NO_LOCK 0
#define SHARED_LOCK 1
#define RESERVED_LOCK 2
#define PENDING_LOCK 3
#define EXCLUSIVE_LOCK 4
加鎖時傳入的參數為連接句柄和鎖的類型,unixLock()實現了具體的流程,pFile->eFileLock表示當前連接的鎖,eFileLock為當前連接需要獲取的鎖,pInode->eFileLock為文件在當前進程中的鎖,以下是這幾個變量的關系
if (pFile->eFileLock>=eFileLock)
return SQLITE_OK
else if(pInode->eFileLock >= PENDING_LOCK)
此時必定有其他線程申請了獨占鎖
return SQLITE_OK
else
switch (eFileLock)
case NO_LOCK:
此時必定有pFile->eFileLock>=eFileLock,所以不
可能出現這種情況。
case SHARED_LOCK:
此時pFile->eFileLock == NO_LOCK,pInode->
eFileLock <= RESERVED_LOCK,可以正常申請鎖
case RESERVED_LOCK:
此時pFile->eFileLock == SHARED_LOCK,如果
pInode->eFileLock==RESERVED_LOCK將返回busy
case EXCLUSIVE_LOCK:
如果pInode->eFileLock==RESERVED_LOCK將返回
busy,不管pFile->eFileLock是什么
源代碼如下: static int unixLock(sqlite3_file *id, int eFileLock){
int rc = SQLITE_OK;
unixFile *pFile = (unixFile*)id;
unixInodeInfo *pInode;
struct flock lock;
int tErrno = 0;
assert( pFile );
// 判斷1:eFileLock比當前鎖的等級高才加鎖
if( pFile->eFileLock>=eFileLock ){
return SQLITE_OK;
}
/* Make sure the locking sequence is correct.
** (1) We never move from unlocked to anything higher than shared lock.
** (2) SQLite never explicitly requests a pendig lock.
** (3) A shared lock is always held when a reserve lock is requested.
*/
//斷言1:當pFile->eFileLock=NO_LOCK時,只能加共享鎖
assert( pFile->eFileLock!=NO_LOCK || eFileLock==SHARED_LOCK );
//斷言2:Pending鎖是內部鎖,不能由外部事務獲取
assert( eFileLock!=PENDING_LOCK );
//斷言3:只有在共享鎖的基礎上才能申請Reserved鎖
assert( eFileLock!=RESERVED_LOCK || pFile->eFileLock==SHARED_LOCK );
/* This mutex is needed because pFile->pInode is shared across threads
*/
unixEnterMutex();
//獲取當前連接對應的inode
pInode = pFile->pInode;
/* If some thread using this PID has a lock via a different unixFile*
** handle that precludes the requested lock, return BUSY.
*/
//如果有其他連接擁有了Pending鎖,返回busy
//由定義可知pInode->eFileLock >= pFile->eFileLock
//當eFileLock>SHARED_LOCK,由斷言1和pFile->eFileLock!=pInode-> eFileLock知pInode->eFileLock> SHARED_LOCK
//當pFile->eFileLock==pInode->eFileLock,那么只能是NO_LOCK或SHARED_LOCK狀態,此時不可能出現排斥狀態
//等價于(pInode->eFileLock>=PENDING_LOCK)||((pFile->eFileLock == RESERVED_LOCK)&&(eFileLock>SHARED_LOCK))
if( (pFile->eFileLock!=pInode->eFileLock &&
(pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK))
){
rc = SQLITE_BUSY;
goto end_lock;
}
/* If a SHARED lock is requested, and some thread using this PID already
** has a SHARED or RESERVED lock, then increment reference counts and
** return SQLITE_OK.
*/
//獲取的是共享鎖,但是其他線程已經擁有共享鎖或Reserved鎖,此時無需對文件加鎖,只需在pInode和pFile里記錄信息即可
if( eFileLock==SHARED_LOCK &&
(pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){
assert( eFileLock==SHARED_LOCK );
assert( pFile->eFileLock==0 );
assert( pInode->nShared>0 );
pFile->eFileLock = SHARED_LOCK;
pInode->nShared++;
pInode->nLock++;
goto end_lock;
}
/* A PENDING lock is needed before acquiring a SHARED lock and before
** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will
** be released.
*/
//如果進程是第一次獲取共享鎖,或者獲取獨占鎖時都需要先獲取Pending鎖
lock.l_len = 1L;
lock.l_whence = SEEK_SET;
if( eFileLock==SHARED_LOCK
|| (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLock<PENDING_LOCK)
){
lock.l_type = (eFileLock==SHARED_LOCK?F_RDLCK:F_WRLCK);
lock.l_start = PENDING_BYTE;
//如果獲取失敗,說明已經有其他進程擁有Pending鎖或獨占鎖
if( unixFileLock(pFile, &lock) ){
tErrno = errno;
rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK);
if( rc!=SQLITE_BUSY ){
storeLastErrno(pFile, tErrno);
}
goto end_lock;
}
}
/* If control gets to this point, then actually go ahead and make
** operating system calls for the specified lock.
*/
if( eFileLock==SHARED_LOCK ){
assert( pInode->nShared==0 );
assert( pInode->eFileLock==0 );
assert( rc==SQLITE_OK );
/* Now get the read-lock */
lock.l_start = SHARED_FIRST;
lock.l_len = SHARED_SIZE;
//此時已經擁有Pending鎖,SHARED_FIRST區域不可能有寫鎖
//所以理論上在SHARED_FIRST區域獲取讀鎖并不會失敗
if( unixFileLock(pFile, &lock) ){
tErrno = errno;
rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK);
}
//獲取到共享鎖后釋放Pending鎖
/* Drop the temporary PENDING lock */
lock.l_start = PENDING_BYTE;
lock.l_len = 1L;
lock.l_type = F_UNLCK;
if( unixFileLock(pFile, &lock) && rc==SQLITE_OK ){
/* This could happen with a network mount */
tErrno = errno;
rc = SQLITE_IOERR_UNLOCK;
}
if( rc ){
if( rc!=SQLITE_BUSY ){
storeLastErrno(pFile, tErrno);
}
goto end_lock;
}else{
//修改鎖的相關信息
pFile->eFileLock = SHARED_LOCK;
pInode->nLock++;
pInode->nShared = 1;
}
}else if( eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1 ){
/* We are trying for an exclusive lock but another thread in this
** same process is still holding a shared lock. */
// pInode->nShared>1說明還有其他線程擁有共享鎖
rc = SQLITE_BUSY;
}else{
/* The request was for a RESERVED or EXCLUSIVE lock. It is
** assumed that there is a SHARED or greater lock on the file
** already.
*/
assert( 0!=pFile->eFileLock );
lock.l_type = F_WRLCK;
assert( eFileLock==RESERVED_LOCK || eFileLock==EXCLUSIVE_LOCK );
if( eFileLock==RESERVED_LOCK ){
lock.l_start = RESERVED_BYTE;
lock.l_len = 1L;
}else{
lock.l_start = SHARED_FIRST;
lock.l_len = SHARED_SIZE;
}
//如果還有其他進程擁有Reserved以上等級的鎖,將會獲取失敗
if( unixFileLock(pFile, &lock) ){
tErrno = errno;
rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK);
if( rc!=SQLITE_BUSY ){
storeLastErrno(pFile, tErrno);
}
}
}
//根據判斷1可知 pInode->eFileLock>=pFile->eFileLock
if( rc==SQLITE_OK ){
pFile->eFileLock = eFileLock;
pInode->eFileLock = eFileLock;
}else if( eFileLock==EXCLUSIVE_LOCK ){
//說明還有其他進程有鎖,等待其釋放
pFile->eFileLock = PENDING_LOCK;
pInode->eFileLock = PENDING_LOCK;
}
end_lock:
unixLeaveMutex();
return rc;
}
unixUnlock的實現就比較簡單,在此簡單說明一下:
if (pFile->eFileLock>=eFileLock)
此時已經有更低級的鎖,無需任何操作
return SQLITE_OK
if(pFile->eFileLock > SHARED_LOCK)
此時根據鎖的協議,不可能存在由獨占鎖降到Reserved鎖
的情況,所以eFileLock為NO_LOCK或共享鎖
if(eFileLock == SHARED_LOCK)
把鎖頁中的Shared byte區域設為讀鎖
清除Pending byte和Reserved byte區域的寫鎖
if(eFileLock == NO_LOCK)
pInode->nShared--
if(pInode->nShared == 0)
清除這個文件的所有鎖
智能推薦
SQLite3的基礎學習以及Python實踐讀寫db文件
Python集成自帶的最精巧的數據庫管理方案。 基礎命令行操作 鏈接:https://pan.baidu.com/s/1hU1nYRSdm8K0JOeQZVMvJQ 密碼:2cyq為Xmind文件。這里的命令雖然全是大寫,但是sqlite3并不區分大小寫,都用小寫也可以。 python基礎操作 這里寫了一個將Name這一個列表進行寫入db文件的操作,首先創建,添加等。 然后將文件導入的...
Coco2dx中sqlite3的使用
當前cocos2dx版本3.17.2 sqlite3 大體上參考這位前輩https://blog.51cto.com/shahdza/1614851 如果自己測試時報這種錯 嚴重性 代碼 說明 項目 文件 行 ...
sqlite3提升執行速度——事務操作
數據庫在執行多條操作時就會顯得速度很慢,為了提升速度可以使用數據庫的事務操作來提升速度 一、事務介紹 1.事務操作分類 2.在執行多次sql操作時可以以下面模板去使用事務 3.demon驗證 1>下面demon使用事務插入1000條信息到表中,用時14毫秒 2>j接下來屏蔽掉事務操作 對比發現屏蔽掉事務后插入速度明顯下降 數據庫 完整demon,可以編譯運行 下載地址...
sqlite3并發讀寫和事務死鎖問題
最近項目中涉及到sqlite并發讀寫的問題,最終發現基線兩個數據庫使用同一個db_connect()接口,都存在并發訪問沖突隱患,但只在H11平臺上出現。是因為其它平臺性能好,“只要你CPU執行速度夠快,我dhcp就能完美錯開sipServer初始化,避免沖突” 。 參考一些文檔并結合自己的實踐,對sqlite3并發問題總結了幾點: sqlite3的鎖及事務類型 sqlit...
3. django的sqlite3設置
1. 每次執行python manage.py runserver都會出現下圖(標紅的信息) 1.1 怎么把這個提示去掉 sqlite3需要先將服務運行一次后才會出現 python manage.py runserver 2. 然后輸入下面的語句 2.1 出現下圖的提示信息 3. 然后再重新運行服務 3.1 這樣就不會出現紅字了 4.1 因為執行了python manage.py migrate,...
猜你喜歡
python3.6找到不_sqlite3模塊
在pytorch中要使用tensorboard,在使用的時候出現一個錯誤,提示: 字面意思,就是找不到sqlite3模塊,所以用了以下方法解決該錯誤。 1.安裝sqlite3 使用sqlite3 --help來判斷是否已經安裝sqlite3,如下所示,表示已經安裝了sqlite3: 如果未安裝,則按如下步驟進行安裝: 這就會將...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...