<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>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## SQLite入門與分析(四)---Page Cache之事務處理(1) 寫在前面:從本章開始,將對SQLite的每個模塊進行討論。討論的順序按照我閱讀SQLite的順序來進行,由于項目的需要,以及時間關系,不能給出一個完整的計劃,但是我會先討論我認為比較重要的內容。本節討論SQLite的事務處理技術,事務處理是DBMS中最關鍵的技術,對SQLite也一樣,它涉及到并發控制,以及故障恢復,由于內容較多,分為兩節。好了,下面進入正題。 本節通過一個具體的例子來分析SQLite原子提交的實現(基于Version 3.3.6的代碼)。 CREATE TABLE episodes( id integer primary key,name text, cid int) ; 插入一條記錄:insert into episodes(name,cid) values("cat",1) ; 它經過編譯器處理后生成的虛擬機代碼如下: ~~~ sqlite> explain insert into episodes(name,cid) values("cat",1); 0|Trace|0|0|0|explain insert into episodes(name,cid) values("cat",1);|00| 1|Goto|0|12|0||00| 2|SetNumColumns|0|3|0||00| 3|OpenWrite|0|2|0||00| 4|NewRowid|0|2|0||00| 5|Null|0|3|0||00| 6|String8|0|4|0|cat|00| 7|Integer|1|5|0||00| 8|MakeRecord|3|3|6|dad|00| 9|Insert|0|6|2|episodes|0b| 10|Close|0|0|0||00| 11|Halt|0|0|0||00| 12|Transaction|0|1|0||00| 13|VerifyCookie|0|1|0||00| 14|Transaction|1|1|0||00| 15|VerifyCookie|1|0|0||00| 16|TableLock|0|2|1|episodes|00| 17|Goto|0|2|0||00| ~~~ ###1、初始狀態(Initial State) 當一個數據庫連接第一次打開時,狀態如圖所示。圖中最右邊(“Disk”標注)表示保存在存儲設備中的內容。每個方框代表一個扇區。藍色的塊表示這個扇區保存了原始數據。圖中中間區域是操作系統的磁盤緩沖區。開始的時候,這些緩存是還沒有被使用,因此這些方框是空白的。圖中左邊區域顯示SQLite用戶進程的內存。因為這個數據庫連接剛剛打開,所以還沒有任何數據記錄被讀入,所以這些內存也是空的。 ![document/2015-09-15/55f7c36622c27](https://box.kancloud.cn/document_2015-09-15_55f7c36622c27.png) ###2、獲取讀鎖(Acquiring A Read Lock) 在SQLite寫數據庫之前,它必須先從數據庫中讀取相關信息。比如,在插入新的數據時,SQLite會先從sqlite_master表中讀取數據庫模式(相當于數據字典),以便編譯器對INSERT語句進行分析,確定數據插入的位置。 在進行讀操作之前,必須先獲取數據庫的共享鎖(shared lock),共享鎖允許兩個或更多的連接在同一時刻讀取數據庫。但是共享鎖不允許其它連接對數據庫進行寫操作。 shared lock存在于操作系統磁盤緩存,而不是磁盤本身。文件鎖的本質只是操作系統的內核數據結構,當操作系統崩潰或掉電時,這些內核數據也會隨之消失。 ![document/2015-09-15/55f7c380a0816](https://box.kancloud.cn/document_2015-09-15_55f7c380a0816.png) ###3、讀取數據 一旦得到shared lock,就可以進行讀操作。如圖所示,數據先由OS從磁盤讀取到OS緩存,然后再由OS移到用戶進程空間。一般來說,數據庫文件分為很多頁,而一次讀操作只讀取一小部分頁面。如圖,從8個頁面讀取3個頁面。 ![document/2015-09-15/55f7c3a07a20f](https://box.kancloud.cn/document_2015-09-15_55f7c3a07a20f.png) ###4、獲取Reserved Lock 在對數據進行修改操作之前,先要獲取數據庫文件的Reserved Lock,Reserved Lock和shared lock的相似之處在于,它們都允許其它進程對數據庫文件進行讀操作。Reserved Lock和Shared Lock可以共存,但是只能是一個Reserved Lock和多個Shared Lock——多個Reserved Lock不能共存。所以,在同一時刻,只能進行一個寫操作。 Reserved Lock意味著當前進程(連接)想修改數據庫文件,但是還沒開始修改操作,所以其它的進程可以讀數據庫,但不能寫數據庫。 ![document/2015-09-15/55f7c3b0c9365](https://box.kancloud.cn/document_2015-09-15_55f7c3b0c9365.png) ###5、創建恢復日志(Creating A Rollback Journal File) 在對數據庫進行寫操作之前,SQLite先要創建一個單獨的日志文件,然后把要修改的頁面的原始數據寫入日志。回滾日志包含一個日志頭(圖中的綠色)——記錄數據庫文件的原始大小。所以即使數據庫文件大小改變了,我們仍知道數據庫的原始大小。 從OS的角度來看,當一個文件創建時,大多數OS(Windows,Linux,Mac OS X)不會向磁盤寫入數據,新創建的文件此時位于磁盤緩存中,之后才會真正寫入磁盤。如圖,日志文件位于OS磁盤緩存中,而不是位于磁盤。 ![document/2015-09-15/55f7c3c163a3a](https://box.kancloud.cn/document_2015-09-15_55f7c3c163a3a.png) ~~~ //事務指令的實現 //p1為數據庫文件的索引號---0為main database;1為temporary tables使用的文件 //p2 不為0,一個寫事務開始 case OP_Transaction: { //數據庫的索引號 int i = pOp->p1; //指向數據庫對應的btree Btree *pBt; assert( i>=0 && i<db->nDb ); assert( (p->btreeMask & (1<<i))!=0 ); //設置btree指針 pBt = db->aDb[i].pBt; if( pBt ){ //從這里btree開始事務,主要給文件加鎖,并設置btree事務狀態 rc = sqlite3BtreeBeginTrans(pBt, pOp->p2); if( rc==SQLITE_BUSY ){ p->pc = pc; p->rc = rc = SQLITE_BUSY; goto vdbe_return; } if( rc!=SQLITE_OK && rc!=SQLITE_READONLY /* && rc!=SQLITE_BUSY */ ){ goto abort_due_to_error; } } break; } //開始一個事務,如果第二個參數不為0,則一個寫事務開始,否則是一個讀事務 //如果wrflag>=2,一個exclusive事務開始,此時別的連接不能訪問數據庫 int sqlite3BtreeBeginTrans(Btree *p, int wrflag){ BtShared *pBt = p->pBt; int rc = SQLITE_OK; btreeIntegrity(p); /* If the btree is already in a write-transaction, or it ** is already in a read-transaction and a read-transaction ** is requested, this is a no-op. */ //如果b-tree處于一個寫事務;或者處于一個讀事務,一個讀事務又請求,則返回SQLITE_OK if( p->inTrans==TRANS_WRITE || (p->inTrans==TRANS_READ && !wrflag) ){ return SQLITE_OK; } /* Write transactions are not possible on a read-only database */ //寫事務不能訪問只讀數據庫 if( pBt->readOnly && wrflag ){ return SQLITE_READONLY; } /* If another database handle has already opened a write transaction ** on this shared-btree structure and a second write transaction is ** requested, return SQLITE_BUSY. */ //如果數據庫已存在一個寫事務,則該寫事務請求時返回SQLITE_BUSY if( pBt->inTransaction==TRANS_WRITE && wrflag ){ return SQLITE_BUSY; } do { //如果數據庫對應btree的第一個頁面還沒讀進內存 //則把該頁面讀進內存,數據庫也相應的加read lock if( pBt->pPage1==0 ){ //加read lock,并讀頁面到內存 rc = lockBtree(pBt); } if( rc==SQLITE_OK && wrflag ){ //對數據庫文件加RESERVED_LOCK鎖 rc = sqlite3pager_begin(pBt->pPage1->aData, wrflag>1); if( rc==SQLITE_OK ){ rc = newDatabase(pBt); } } if( rc==SQLITE_OK ){ if( wrflag ) pBt->inStmt = 0; }else{ unlockBtreeIfUnused(pBt); } }while( rc==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE && sqlite3InvokeBusyHandler(pBt->pBusyHandler) ); if( rc==SQLITE_OK ){ if( p->inTrans==TRANS_NONE ){ //btree的事務數加1 pBt->nTransaction++; } //設置btree事務狀態 p->inTrans = (wrflag?TRANS_WRITE:TRANS_READ); if( p->inTrans>pBt->inTransaction ){ pBt->inTransaction = p->inTrans; } } btreeIntegrity(p); return rc; } /* **獲取數據庫的寫鎖,發生以下情況時去除寫鎖: ** * sqlite3pager_commit() is called. ** * sqlite3pager_rollback() is called. ** * sqlite3pager_close() is called. ** * sqlite3pager_unref() is called to on every outstanding page. ** pData指向數據庫的打開的頁面,此時并不修改,僅僅只是獲取 ** 相應的pager,檢查它是否處于read-lock狀態。 **如果打開的不是臨時文件,則打開日志文件. **如果數據庫已經處于寫狀態,則do nothing */ int sqlite3pager_begin(void *pData, int exFlag){ PgHdr *pPg = DATA_TO_PGHDR(pData); Pager *pPager = pPg->pPager; int rc = SQLITE_OK; assert( pPg->nRef>0 ); assert( pPager->state!=PAGER_UNLOCK ); //pager已經處于share狀態 if( pPager->state==PAGER_SHARED ){ assert( pPager->aInJournal==0 ); if( MEMDB ){ pPager->state = PAGER_EXCLUSIVE; pPager->origDbSize = pPager->dbSize; }else{ //對文件加 RESERVED_LOCK rc = sqlite3OsLock(pPager->fd, RESERVED_LOCK); if( rc==SQLITE_OK ){ //設置pager的狀態 pPager->state = PAGER_RESERVED; if( exFlag ){ rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); } } if( rc!=SQLITE_OK ){ return rc; } pPager->dirtyCache = 0; TRACE2("TRANSACTION %d\n", PAGERID(pPager)); //使用日志,不是臨時文件,則打開日志文件 if( pPager->useJournal && !pPager->tempFile ){ //為pager打開日志文件,pager應該處于RESERVED或EXCLUSIVE狀態 //會向日志文件寫入header rc = pager_open_journal(pPager); } } } return rc; } //創建日志文件,pager應該處于RESERVED或EXCLUSIVE狀態 static int pager_open_journal(Pager *pPager){ int rc; assert( !MEMDB ); assert( pPager->state>=PAGER_RESERVED ); assert( pPager->journalOpen==0 ); assert( pPager->useJournal ); assert( pPager->aInJournal==0 ); sqlite3pager_pagecount(pPager); //日志文件頁面位圖 pPager->aInJournal = sqliteMalloc( pPager->dbSize/8 + 1 ); if( pPager->aInJournal==0 ){ rc = SQLITE_NOMEM; goto failed_to_open_journal; } //打開日志文件 rc = sqlite3OsOpenExclusive(pPager->zJournal, &pPager->jfd, pPager->tempFile); //日志文件的位置指針 pPager->journalOff = 0; pPager->setMaster = 0; pPager->journalHdr = 0; if( rc!=SQLITE_OK ){ goto failed_to_open_journal; } /*一般來說,os此時創建的文件位于磁盤緩存,并沒有實際 **存在于磁盤,下面三個操作就是為了把結果寫入磁盤,而對于 **windows系統來說,并沒有提供相應API,所以實際上沒有意義. */ //fullSync操作對windows沒有意義 sqlite3OsSetFullSync(pPager->jfd, pPager->full_fsync); sqlite3OsSetFullSync(pPager->fd, pPager->full_fsync); /* Attempt to open a file descriptor for the directory that contains a file. **This file descriptor can be used to fsync() the directory **in order to make sure the creation of a new file is actually written to disk. */ sqlite3OsOpenDirectory(pPager->jfd, pPager->zDirectory); pPager->journalOpen = 1; pPager->journalStarted = 0; pPager->needSync = 0; pPager->alwaysRollback = 0; pPager->nRec = 0; if( pPager->errCode ){ rc = pPager->errCode; goto failed_to_open_journal; } pPager->origDbSize = pPager->dbSize; //寫入日志文件的header---24個字節 rc = writeJournalHdr(pPager); if( pPager->stmtAutoopen && rc==SQLITE_OK ){ rc = sqlite3pager_stmt_begin(pPager); } if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM ){ rc = pager_unwritelock(pPager); if( rc==SQLITE_OK ){ rc = SQLITE_FULL; } } return rc; failed_to_open_journal: sqliteFree(pPager->aInJournal); pPager->aInJournal = 0; if( rc==SQLITE_NOMEM ){ /* If this was a malloc() failure, then we will not be closing the pager ** file. So delete any journal file we may have just created. Otherwise, ** the system will get confused, we have a read-lock on the file and a ** mysterious journal has appeared in the filesystem. */ sqlite3OsDelete(pPager->zJournal); }else{ sqlite3OsUnlock(pPager->fd, NO_LOCK); pPager->state = PAGER_UNLOCK; } return rc; } /*寫入日志文件頭 **journal header的格式如下: ** - 8 bytes: 標志日志文件的魔數 ** - 4 bytes: 日志文件中記錄數 ** - 4 bytes: Random number used for page hash. ** - 4 bytes: 原來數據庫的大小(kb) ** - 4 bytes: 扇區大小512byte */ static int writeJournalHdr(Pager *pPager){ //日志文件頭 char zHeader[sizeof(aJournalMagic)+16]; int rc = seekJournalHdr(pPager); if( rc ) return rc; pPager->journalHdr = pPager->journalOff; if( pPager->stmtHdrOff==0 ){ pPager->stmtHdrOff = pPager->journalHdr; } //設置文件指針指向header之后 pPager->journalOff += JOURNAL_HDR_SZ(pPager); /* FIX ME: ** ** Possibly for a pager not in no-sync mode, the journal magic should not ** be written until nRec is filled in as part of next syncJournal(). ** ** Actually maybe the whole journal header should be delayed until that ** point. Think about this. */ memcpy(zHeader, aJournalMagic, sizeof(aJournalMagic)); /* The nRec Field. 0xFFFFFFFF for no-sync journals. */ put32bits(&zHeader[sizeof(aJournalMagic)], pPager->noSync ? 0xffffffff : 0); /* The random check-hash initialiser */ sqlite3Randomness(sizeof(pPager->cksumInit), &pPager->cksumInit); put32bits(&zHeader[sizeof(aJournalMagic)+4], pPager->cksumInit); /* The initial database size */ put32bits(&zHeader[sizeof(aJournalMagic)+8], pPager->dbSize); /* The assumed sector size for this process */ put32bits(&zHeader[sizeof(aJournalMagic)+12], pPager->sectorSize); //寫入文件頭 rc = sqlite3OsWrite(pPager->jfd, zHeader, sizeof(zHeader)); /* The journal header has been written successfully. Seek the journal ** file descriptor to the end of the journal header sector. */ if( rc==SQLITE_OK ){ rc = sqlite3OsSeek(pPager->jfd, pPager->journalOff-1); if( rc==SQLITE_OK ){ rc = sqlite3OsWrite(pPager->jfd, "\000", 1); } } return rc; } ~~~ 其實現過程如下圖所示: ![document/2015-09-15/55f7c3eba1fd4](https://box.kancloud.cn/document_2015-09-15_55f7c3eba1fd4.png)
                  <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>

                              哎呀哎呀视频在线观看