• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • SQLite3源碼學習(25) Pager模塊之事務鎖的實現2

    標簽: sqlite  Pager  事務    

    在上一篇文章中介紹了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個事務。

    clip_image002

    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)
             清除這個文件的所有鎖
    



     

     

      

    版權聲明:本文為pfysw原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/pfysw/article/details/80119280

    智能推薦

    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:   如果未安裝,則按如下步驟進行安裝: 這就會將...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    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壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    精品国产乱码久久久久久蜜桃不卡