<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## SQLite入門與分析(五)---Page Cache之并發控制 寫在前面:本節主要談談SQLite的鎖機制,SQLite是基于鎖來實現并發控制的,所以本節的內容實際上是屬于事務處理的,但是SQLite的鎖機制實現非常的簡單而巧妙,所以在這里單獨討論一下。如果真正理解了它,對整個事務的實現也就理解了。而要真正理解SQLite的鎖機制,最好方法就是閱讀SQLite的源碼,所以在閱讀本文時,最好能結合源碼。SQLite的鎖機制很巧妙,盡管在本節中的源碼中,我寫了很多注釋,也是我個人在研究時的一點心得,但是我發現僅僅用言語,似乎不能把問題說清楚,只有通過體會,才能真正理解SQLite的鎖機制。好了,下面進入正題。 SQLite的并發控制機制是采用加鎖的方式,實現非常簡單,但也非常的巧妙,本節將對其進行一個詳細的解剖。請仔細閱讀下圖,它可以幫助更好的理解下面的內容。 ![document/2015-09-15/55f7c72bec8ac](https://box.kancloud.cn/document_2015-09-15_55f7c72bec8ac.png) ###1、RESERVED LOCK RESERVED鎖意味著進程將要對數據庫進行寫操作。某一時刻只能有一個RESERVED Lock,但是RESERVED鎖和SHARED鎖可以共存,而且可以對數據庫加新的SHARED鎖。 為什么要用RESERVED鎖? 主要是出于并發性的考慮。由于SQLite只有庫級排斥鎖(EXCLUSIVE LOCK),如果寫事務一開始就上EXCLUSIVE鎖,然后再進行實際的數據更新,寫磁盤操作,這會使得并發性大大降低。而SQLite一旦得到數據庫的RESERVED鎖,就可以對緩存中的數據進行修改,而與此同時,其它進程可以繼續進行讀操作。直到真正需要寫磁盤時才對數據庫加EXCLUSIVE鎖。 ###2、PENDING LOCK PENDING LOCK意味著進程已經完成緩存中的數據修改,并想立即將更新寫入磁盤。它將等待此時已經存在的讀鎖事務完成,但是不允許對數據庫加新的SHARED LOCK(這與RESERVED LOCK相區別)。 為什么要有PENDING LOCK? 主要是為了防止出現寫餓死的情況。由于寫事務先要獲取RESERVED LOCK,所以可能一直產生新的SHARED LOCK,使得寫事務發生餓死的情況。 ###3、加鎖機制的具體實現 SQLite在pager層獲取鎖的函數如下: ~~~ //獲取一個文件的鎖,如果忙則重復該操作, //直到busy 回調用函數返回flase,或者成功獲得鎖 static int pager_wait_on_lock(Pager *pPager, int locktype){ int rc; assert( PAGER_SHARED==SHARED_LOCK ); assert( PAGER_RESERVED==RESERVED_LOCK ); assert( PAGER_EXCLUSIVE==EXCLUSIVE_LOCK ); if( pPager->state>=locktype ){ rc = SQLITE_OK; }else{ //重復直到獲得鎖 do { rc = sqlite3OsLock(pPager->fd, locktype); }while( rc==SQLITE_BUSY && sqlite3InvokeBusyHandler(pPager->pBusyHandler) ); if( rc==SQLITE_OK ){ //設置pager的狀態 pPager->state = locktype; } } return rc; } ~~~ Windows下具體的實現如下: ~~~ static int winLock(OsFile *id, int locktype){ int rc = SQLITE_OK; /* Return code from subroutines */ int res = 1; /* Result of a windows lock call */ int newLocktype; /* Set id->locktype to this value before exiting */ int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */ winFile *pFile = (winFile*)id; assert( pFile!=0 ); TRACE5("LOCK %d %d was %d(%d)\n", pFile->h, locktype, pFile->locktype, pFile->sharedLockByte); /* If there is already a lock of this type or more restrictive on the ** OsFile, do nothing. Don't use the end_lock: exit path, as ** sqlite3OsEnterMutex() hasn't been called yet. */ //當前的鎖>=locktype,則返回 if( pFile->locktype>=locktype ){ return SQLITE_OK; } /* Make sure the locking sequence is correct */ assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK ); assert( locktype!=PENDING_LOCK ); assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK ); /* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or ** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of ** the PENDING_LOCK byte is temporary. */ newLocktype = pFile->locktype; /*兩種情況: (1)如果當前文件處于無鎖狀態(獲取讀鎖---讀事務 **和寫事務在最初階段都要經歷的階段), **(2)處于RESERVED_LOCK,且請求的鎖為EXCLUSIVE_LOCK(寫事務) **則對執行加PENDING_LOCK */ /////////////////////(1)/////////////////// if( pFile->locktype==NO_LOCK || (locktype==EXCLUSIVE_LOCK && pFile->locktype==RESERVED_LOCK) ){ int cnt = 3; //加pending鎖 while( cnt-->0 && (res = LockFile(pFile->h, PENDING_BYTE, 0, 1, 0))==0 ){ /* Try 3 times to get the pending lock. The pending lock might be ** held by another reader process who will release it momentarily. */ TRACE2("could not get a PENDING lock. cnt=%d\n", cnt); Sleep(1); } //設置為gotPendingLock為1,使和在后面要釋放PENDING鎖 gotPendingLock = res; } /* Acquire a shared lock */ /*獲取shared lock **此時,事務應該持有PENDING鎖,而PENDING鎖作為事務從UNLOCKED到 **SHARED_LOCKED的一個過渡,所以事務由PENDING->SHARED **此時,實際上鎖處于兩個狀態:PENDING和SHARED, **直到后面釋放PENDING鎖后,才真正處于SHARED狀態 */ ////////////////(2)///////////////////////////////////// if( locktype==SHARED_LOCK && res ){ assert( pFile->locktype==NO_LOCK ); res = getReadLock(pFile); if( res ){ newLocktype = SHARED_LOCK; } } /* Acquire a RESERVED lock */ /*獲取RESERVED **此時事務持有SHARED_LOCK,變化過程為SHARED->RESERVED。 **RESERVED鎖的作用就是為了提高系統的并發性能 */ ////////////////////////(3)///////////////////////////////// if( locktype==RESERVED_LOCK && res ){ assert( pFile->locktype==SHARED_LOCK ); //加RESERVED鎖 res = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); if( res ){ newLocktype = RESERVED_LOCK; } } /* Acquire a PENDING lock */ /*獲取PENDING鎖 **此時事務持有RESERVED_LOCK,且事務申請EXCLUSIVE_LOCK **變化過程為:RESERVED->PENDING。 **PENDING狀態只是唯一的作用就是防止寫餓死. **讀事務不會執行該代碼,但是寫事務會執行該代碼, **執行該代碼后gotPendingLock設為0,后面就不會釋放PENDING鎖。 */ //////////////////////////////(4)//////////////////////////////// if( locktype==EXCLUSIVE_LOCK && res ){ //這里沒有實際的加鎖操作,只是把鎖的狀態改為PENDING狀態 newLocktype = PENDING_LOCK; //設置了gotPendingLock,后面就不會釋放PENDING鎖了, //相當于加了PENDING鎖,實際上是在開始處加的PENDING鎖 gotPendingLock = 0; } /* Acquire an EXCLUSIVE lock */ /*獲取EXCLUSIVE鎖 **當一個事務執行該代碼時,它應該滿足以下條件: **(1)鎖的狀態為:PENDING (2)是一個寫事務 **變化過程:PENDING->EXCLUSIVE */ /////////////////////////(5)/////////////////////////////////////////// if( locktype==EXCLUSIVE_LOCK && res ){ assert( pFile->locktype>=SHARED_LOCK ); res = unlockReadLock(pFile); TRACE2("unreadlock = %d\n", res); res = LockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); if( res ){ newLocktype = EXCLUSIVE_LOCK; }else{ TRACE2("error-code = %d\n", GetLastError()); } } /* If we are holding a PENDING lock that ought to be released, then ** release it now. */ /*此時事務在第2步中獲得PENDING鎖,它將申請SHARED_LOCK(第3步,和圖形相對照), **而在之前它已經獲取了PENDING鎖, **所以在這里它需要釋放PENDING鎖,此時鎖的變化為:PENDING->SHARED */ //////////////////////////(6)///////////////////////////////////// if( gotPendingLock && locktype==SHARED_LOCK ){ UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0); } /* Update the state of the lock has held in the file descriptor then ** return the appropriate result code. */ if( res ){ rc = SQLITE_OK; }else{ TRACE4("LOCK FAILED %d trying for %d but got %d\n", pFile->h, locktype, newLocktype); rc = SQLITE_BUSY; } //在這里設置文件鎖的狀態 pFile->locktype = newLocktype; return rc; } ~~~ 在幾個關鍵的部位標記數字。 (I)對于一個讀事務會的完整經過: 語句序列:(1)——>(2)——>(6) 相應的狀態真正的變化過程為:UNLOCKED→PENDING(1)→PENDING、SHARED(2)→SHARED(6)→UNLOCKED (II)對于一個寫事務完整經過: 第一階段: 語句序列:(1)——>(2)——>(6) 狀態變化:UNLOCKED→PENDING(1)→PENDING、SHARED(2)→SHARED(6)。此時事務獲得SHARED LOCK。 第二個階段: 語句序列:(3) 此時事務獲得RESERVED LOCK。 第三個階段: 事務執行修改操作。 第四個階段: 語句序列:(1)——>(4)——>(5) 狀態變化為: RESERVED→ RESERVED 、PENDING(1)→PENDING(4)→EXCLUSIVE(5)。此時事務獲得排斥鎖,就可以進行寫磁盤操作了。 注:在上面的過程中,由于(1)的執行,使得某些時刻SQLite處于兩種狀態,但它持續的時間很短,從某種程度上來說可以忽略,但是為了把問題說清楚,在這里描述了這一微妙而巧妙的過程。 ###4、SQLite的死鎖問題 SQLite的加鎖機制會不會出現死鎖? 這是一個很有意思的問題,對于任何采取加鎖作為并發控制機制的DBMS都得考慮這個問題。有兩種方式處理死鎖問題:(1)死鎖預防(deadlock prevention)(2)死鎖檢測(deadlock detection)與死鎖恢復(deadlock recovery)。SQLite采取了第一種方式,如果一個事務不能獲取鎖,它會重試有限次(這個重試次數可以由應用程序運行預先設置,默認為1次)——這實際上是基本鎖超時的機制。如果還是不能獲取鎖,SQLite返回SQLITE_BUSY錯誤給應用程序,應用程序此時應該中斷,之后再重試;或者中止當前事務。雖然基于鎖超時的機制簡單,容易實現,但是它的缺點也是明顯的——資源浪費。 ###5、事務類型(Transaction Types) 既然SQLite采取了這種機制,所以應用程序得處理SQLITE_BUSY 錯誤,先來看一個會產生SQLITE_BUSY錯誤的例子: ![document/2015-09-15/55f7c87be55f2](https://box.kancloud.cn/document_2015-09-15_55f7c87be55f2.png) 所以應用程序應該盡量避免產生死鎖,那么應用程序如何做可以避免死鎖的產生呢? 答案就是為你的程序選擇正確合適的事務類型。 SQLite有三種不同的事務類型,這不同于鎖的狀態。事務可以從DEFERRED,IMMEDIATE或者EXCLUSIVE,一個事務的類型在BEGIN命令中指定: BEGIN [ DEFERRED | IMMEDIATE | EXCLUSIVE ] TRANSACTION; 一個deferred事務不獲取任何鎖,直到它需要鎖的時候,而且BEGIN語句本身也不會做什么事情——它開始于UNLOCK狀態;默認情況下是這樣的。如果僅僅用BEGIN開始一個事務,那么事務就是DEFERRED的,同時它不會獲取任何鎖,當對數據庫進行第一次讀操作時,它會獲取SHARED LOCK;同樣,當進行第一次寫操作時,它會獲取RESERVED LOCK。 由BEGIN開始的Immediate事務會試著獲取RESERVED LOCK。如果成功,BEGIN IMMEDIATE保證沒有別的連接可以寫數據庫。但是,別的連接可以對數據庫進行讀操作,但是RESERVED LOCK會阻止其它的連接BEGIN IMMEDIATE或者BEGIN EXCLUSIVE命令,SQLite會返回SQLITE_BUSY錯誤。這時你就可以對數據庫進行修改操作,但是你不能提交,當你COMMIT時,會返回SQLITE_BUSY錯誤,這意味著還有其它的讀事務沒有完成,得等它們執行完后才能提交事務。 Exclusive事務會試著獲取對數據庫的EXCLUSIVE鎖。這與IMMEDIATE類似,但是一旦成功,EXCLUSIVE事務保證沒有其它的連接,所以就可對數據庫進行讀寫操作了。 上面那個例子的問題在于兩個連接最終都想寫數據庫,但是他們都沒有放棄各自原來的鎖,最終,shared 鎖導致了問題的出現。如果兩個連接都以BEGIN IMMEDIATE開始事務,那么死鎖就不會發生。在這種情況下,在同一時刻只能有一個連接進入BEGIN IMMEDIATE,其它的連接就得等待。BEGIN IMMEDIATE和BEGIN EXCLUSIVE通常被寫事務使用。就像同步機制一樣,它防止了死鎖的產生。 基本的準則是:如果你在使用的數據庫沒有其它的連接,用BEGIN就足夠了。但是,如果你使用的數據庫在其它的連接也要對數據庫進行寫操作,就得使用BEGIN IMMEDIATE或BEGIN EXCLUSIVE開始你的事務。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看