SQLite入门与分析(四)---Page Cache之事务处理(1)
写在前面:从本章开始,将对SQLite的每个模块进行讨论。讨论的顺序按照我阅读SQLite的顺序来进行,由于项目的需要,以及时间关系,不能给出一个完整的计划,但是我会先讨论我认为比较重要的内容。本节讨论SQLite的事务处理技术,事务处理是DBMS中最关键的技术,对SQLite也一样,它涉及到并发控制,以及故障恢复,由于内容较多,分为两节。好了,下面进入正题。 本节通过一个具体的例子来分析SQLite原子提交的实现(基于Version 3.3.6的代码)。 16|TableLock|0|2|1|episodes|00| 17|Goto|0|2|0||00|
1、初始状态(Initial State) 2、获取读锁(Acquiring A Read Lock) 3、读取数据 4、获取Reserved Lock 5、创建恢复日志(Creating A Rollback Journal File) 上面 5步的代码的实现: //事务指令的实现 p1为数据库文件的索引号---0为maindatabase;1为temporarytables使用的文件 p2不为0,一个写事务开始 caseOP_Transaction:{ 数据库的索引号 inti=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; gotovdbe_return; } if(rc!=SQLITE_OK&&rc!=SQLITE_READONLY/*&&rc!=SQLITE_BUSY*/){ gotoabort_due_to_error; } } break; } 开始一个事务,如果第二个参数不为0,则一个写事务开始,否则是一个读事务 如果wrflag>=2,一个exclusive事务开始,此时别的连接不能访问数据库intsqlite3BtreeBeginTrans(Btree*p,intwrflag){ BtShared*pBt=p->pBt; intrc=SQLITE_OK; btreeIntegrity(p); Ifthebtreeisalreadyinawrite-transaction,orit **isalreadyinaread-transactionandaread-transaction **isrequested,thisisano-op. */ 如果b-tree处于一个写事务;或者处于一个读事务,一个读事务又请求,则返回SQLITE_OKif(p->inTrans==TRANS_WRITE||(p->inTrans==TRANS_READ&&!wrflag)){ returnSQLITE_OK; } Writetransactionsarenotpossibleonaread-onlydatabase写事务不能访问只读数据库if(pBt->readOnly&&wrflag){ returnSQLITE_READONLY; } Ifanotherdatabasehandlehasalreadyopenedawritetransaction **onthisshared-btreestructureandasecondwritetransactionis **requested,returnSQLITE_BUSY. 如果数据库已存在一个写事务,则该写事务请求时返回SQLITE_BUSYif(pBt->inTransaction==TRANS_WRITE&&wrflag){ returnSQLITE_BUSY; } do{ 如果数据库对应btree的第一个页面还没读进内存 则把该页面读进内存,数据库也相应的加readlockif(pBt->pPage1==0){ 加readlock,并读页面到内存 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(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); returnrc; } **获取数据库的写锁,发生以下情况时去除写锁: ***sqlite3pager_commit()iscalled. ***sqlite3pager_rollback()iscalled. ***sqlite3pager_close()iscalled. ***sqlite3pager_unref()iscalledtooneveryoutstandingpage. **pData指向数据库的打开的页面,此时并不修改,仅仅只是获取 **相应的pager,检查它是否处于read-lock状态。 **如果打开的不是临时文件,则打开日志文件. **如果数据库已经处于写状态,则donothing intsqlite3pager_begin(void*pData,255)">intexFlag){ PgHdr*pPg=DATA_TO_PGHDR(pData); Pager*pPager=pPg->pPager; intrc=SQLITE_OK; assert(pPg->nRef>0); assert(pPager->state!=PAGER_UNLOCK); pager已经处于share状态if(pPager->state==PAGER_SHARED){ assert(pPager->aInJournal==if(MEMDB){ pPager->state=PAGER_EXCLUSIVE; pPager->origDbSize=pPager->dbSize; }else{ 对文件加RESERVED_LOCK rc=sqlite3OsLock(pPager->fd,RESERVED_LOCK); 设置pager的状态 pPager->state=PAGER_RESERVED; if(exFlag){ rc=pager_wait_on_lock(pPager,EXCLUSIVE_LOCK); } } if(rc!=SQLITE_OK){ returnrc; } pPager->dirtyCache=0; TRACE2("TRANSACTION%dn",PAGERID(pPager)); 使用日志,不是临时文件,则打开日志文件if(pPager->useJournal&&!pPager->tempFile){ 为pager打开日志文件,pager应该处于RESERVED或EXCLUSIVE状态 会向日志文件写入header rc=pager_open_journal(pPager); } } } returnrc; } 创建日志文件,pager应该处于RESERVED或EXCLUSIVE状态staticintpager_open_journal(Pager*pPager){ intrc; 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+if(pPager->aInJournal==0){ rc=SQLITE_NOMEM; gotofailed_to_open_journal; } 打开日志文件 rc=sqlite3OsOpenExclusive(pPager->zJournal,&pPager->jfd, pPager->tempFile); 日志文件的位置指针 pPager->journalOff=0; pPager->setMaster=0; pPager->journalHdr=0; 一般来说,os此时创建的文件位于磁盘缓存,并没有实际 **存在于磁盘,下面三个操作就是为了把结果写入磁盘,而对于 **windows系统来说,并没有提供相应API,所以实际上没有意义. fullSync操作对windows没有意义 sqlite3OsSetFullSync(pPager->jfd,pPager->full_fsync); sqlite3OsSetFullSync(pPager->fd,pPager->full_fsync); Attempttoopenafiledescriptorforthedirectorythatcontainsafile. **Thisfiledescriptorcanbeusedtofsync()thedirectory **inordertomakesurethecreationofanewfileisactuallywrittentodisk. */ sqlite3OsOpenDirectory(pPager->jfd,pPager->zDirectory); pPager->journalOpen=1; pPager->journalStarted=0; pPager->needSync=0; pPager->alwaysRollback=0; pPager->nRec=if(pPager->errCode){ rc=pPager->errCode; gotofailed_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; } } returnrc; failed_to_open_journal: sqliteFree(pPager->aInJournal); pPager->aInJournal=if(rc==SQLITE_NOMEM){ Ifthiswasamalloc()failure,thenwewillnotbeclosingthepager **file.Sodeleteanyjournalfilewemayhavejustcreated.Otherwise, **thesystemwillgetconfused,wehavearead-lockonthefileanda **mysteriousjournalhasappearedinthefilesystem. */ sqlite3OsDelete(pPager->zJournal); }else{ sqlite3OsUnlock(pPager->fd,NO_LOCK); pPager->state=PAGER_UNLOCK; } 写入日志文件头 **journalheader的格式如下: **-8bytes:标志日志文件的魔数 **-4bytes:日志文件中记录数 **-4bytes:Randomnumberusedforpagehash. **-4bytes:原来数据库的大小(kb) **-4bytes:扇区大小512byte intwriteJournalHdr(Pager*pPager){ 日志文件头charzHeader[sizeof(aJournalMagic)+16]; intrc=seekJournalHdr(pPager); if(rc)returnrc; pPager->journalHdr=pPager->journalOff; if(pPager->stmtHdrOff==0){ pPager->stmtHdrOff=pPager->journalHdr; } 设置文件指针指向header之后 pPager->journalOff+=JOURNAL_HDR_SZ(pPager); FIXME: ** **Possiblyforapagernotinno-syncmode,thejournalmagicshouldnot **bewrittenuntilnRecisfilledinaspartofnextsyncJournal(). ** **Actuallymaybethewholejournalheadershouldbedelayeduntilthat **point.Thinkaboutthis. */ memcpy(zHeader,aJournalMagic,255)">sizeof(aJournalMagic)); ThenRecField.0xFFFFFFFFforno-syncjournals.*/ put32bits(&zHeader[sizeof(aJournalMagic)],pPager->noSync?0xffffffff:Therandomcheck-hashinitialiser*/ sqlite3Randomness(sizeof(pPager->cksumInit),&pPager->cksumInit); put32bits(&zHeader[4],pPager->cksumInit); Theinitialdatabasesize8],pPager->dbSize); Theassumedsectorsizeforthisprocess12],pPager->sectorSize); 写入文件头 rc=sqlite3OsWrite(pPager->jfd,zHeader,255)">sizeof(zHeader)); Thejournalheaderhasbeenwrittensuccessfully.Seekthejournal **filedescriptortotheendofthejournalheadersector. if(rc==SQLITE_OK){ rc=sqlite3OsSeek(pPager->jfd,pPager->journalOff-if(rc==SQLITE_OK){ rc=sqlite3OsWrite(pPager->jfd, |