SQLite入门与分析(四)---Page Cache之事务处理(2)

前端之家收集整理的这篇文章主要介绍了SQLite入门与分析(四)---Page Cache之事务处理(2)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

写在前面:个人认为pager层是sqlite实现最为核心的模块,它具有四大功能:I/O,页面缓存,并发控制和日志恢复。而这些功能不仅是上层Btree的基础,而且对系统的性能和健壮性有关至关重要的影响。其中并发控制和日志恢复是事务处理实现的基础。sqlite并发控制的机制非常简单——封锁机制;别外,它查询优化机制也非常简单——基于索引。这一切使得整个sqlite的实现变得简单,sqlite变得很小,运行速度也非常快,所以,特别适合嵌入式设备。好了,接下来讨论事务的剩余部分。
6、修改位于用户进程空间的页面(Changing Database Pages In User Space)
页面的原始数据写入日志之后,就可以修改页面了——位于用户进程空间。每个数据库连接都有自己私有的空间,所以页面的变化只对该连接可见,而对其它连接的数据仍然是磁盘缓存中的数据。从这里可以明白一件事:一个进程在修改页面数据的同时,其它进程可以继续进行读操作。图中的红色表示修改页面


7、日志文件刷入磁盘(Flushing The Rollback Journal File To Mass Storage)
接下来把日志文件内容刷入磁盘,这对于数据库从意外中恢复来说是至关重要的一步。而且这通常也是一个耗时的操作,因为磁盘I/O速度很慢。
这个步骤不只把日志文件刷入磁盘那么简单,它的实现实际上分成两步:首先把日志文件内容刷入磁盘(即页面数据);然后把日志文件页面的数目写入日志文件头,再把header刷入磁盘(这一过程在代码中清晰可见)。

代码如下:


/*
**Sync日志文件,保证所有的脏页面写入磁盘日志文件
*/
staticintsyncJournal(Pager*pPager){
PgHdr
*pPg;
intrc=sqlITE_OK;

Syncthejournalbeforemodifyingthemaindatabase
**(assumingthereisajournalanditneedstobesynced.)
if(pPager->needSync){
if(!pPager->tempFile){
assert(pPager
->journalOpen);
assert(!pPager->noSync);//noSyncmightbesetifsynchronous
**wasturnedoffafterthetransactionwasstarted.Ticket#615
*/
#ifndefNDEBUG
{
MakesurethepPager->nReccounterwearekeepingagrees
**withthenReccomputedfromthesizeofthejournalfile.
*/
i64jSz;
rc
=sqlite3OsFileSize(pPager->jfd,&jSz);
if(rc!=0)returnrc;
assert(pPager
->journalOff==jSz);
}
#endif
{
WritethenRecvalueintothejournalfileheader.Ifin
**full-synchronousmode,syncthejournalfirst.Thisensuresthat
**alldatahasreallyhitthediskbeforenRecisupdatedtomark
**itasacandidateforrollback.
if(pPager->fullSync){
TRACE2(
"SYNCjournalof%d\n",PAGERID(pPager));
//首先保证脏页面中所有的数据都已经写入日志文件
rc=sqlite3OsSync(pPager->jfd,0);
returnrc;
}
rc
=sqlite3OsSeek(pPager->jfd,
pPager
->journalHdr+sizeof(aJournalMagic));
if(rc)returnrc;
页面的数目写入日志文件
rc=write32bits(pPager->jfd,pPager->nRec);
returnrc;

rc
=sqlite3OsSeek(pPager->jfd,pPager->journalOff);
returnrc;
}
TRACE2(
rc=sqlite3OsSync(pPager->jfd,pPager->full_fsync);
returnrc;
pPager
->journalStarted=1;
}
pPager
->needSync=0;

ErasetheneedSyncflagfromeverypage.
清除needSync标志位
for(pPg=pPager->pAll;pPg;pPg=pPg->pNextAll){
pPg
->needSync=0;
}
pPager
->pFirstSynced=pPager->pFirst;
}

#ifndefNDEBUG
IfthePager.needSyncflagisclearthenthePgHdr.needSync
**flagmustalsobeclearforallpages.Verifythatthis
**invariantistrue.
else{
for(pPg=pPager->pAll;pPg;pPg=pPg->pNextAll){
assert(pPg
->needSync==0);
}
assert(pPager
->pFirstSynced==pPager->pFirst);
}
#endif

returnrc;
}

8、获取排斥锁(Obtaining An Exclusive Lock)
在对数据库文件进行修改之前(注:这里不是内存中的页面),我们必须得到数据库文件的排斥锁(Exclusive Lock)。得到排斥锁的过程可分为两步:首先得到Pending lock;然后Pending lock升级到exclusive lock。
Pending lock允许其它已经存在的Shared lock继续读数据库文件,但是不允许产生新的shared lock,这样做目的是为了防止写操作发生饿死情况。一旦所有的shared lock完成操作,则pending lock升级到exclusive lock。

9、修改页面写入文件(Writing Changes To The Database File)
一旦得到exclusive lock,其它的进程就不能进行读操作,此时就可以把修改页面写回数据库文件,但是通常OS都把结果暂时保存到磁盘缓存中,直到某个时刻才会真正把结果写入磁盘。

以上两步的实现代码


把所有的脏页面写入数据库
到这里开始获取EXCLUSIVEQ锁,并将页面写回操作系统文件
intpager_write_pagelist(PgHdr*pList){
Pager
*pPager;
intrc;

if(pList==returnsqlITE_OK;
pPager
=pList->pPager;

AtthispointtheremaybeeitheraRESERVEDorEXCLUSIVElockonthe
**databasefile.IfthereisalreadyanEXCLUSIVElock,thefollowing
**callstosqlite3OsLock()areno-ops.
**
**MovingthelockfromRESERVEDtoEXCLUSIVEactuallyinvolvesgoing
**throughanintermediatestatePENDING.APENDINGlockpreventsnew
**readersfromattachingtothedatabasebutisunsufficientforusto
**write.TheideaofaPENDINGlockistopreventnewreadersfrom
**cominginwhilewewaitforexistingreaderstoclear.
**
**WhilethepagerisintheRESERVEDstate,theoriginaldatabasefile
**isunchangedandwecanrollbackwithouthavingtoplaybackthe
**journalintotheoriginaldatabasefile.Oncewetransitionto
**EXCLUSIVE,itmeansthedatabasefilehasbeenchangedandanyrollback
**willrequireajournalplayback.
加EXCLUSIVE_LOCK锁
rc=pager_wait_on_lock(pPager,EXCLUSIVE_LOCK);
if(rc!=sqlITE_OK){
returnrc;
}

while(pList){
assert(pList
->dirty);
rc
=sqlite3OsSeek(pPager->fd,(pList->pgno-1)*(i64)pPager->pageSize);
Iftherearedirtypagesinthepagecachewithpagenumbersgreater
**thanPager.dbSize,thismeanssqlite3pager_truncate()wascalledto
**makethefilesmaller(presumablybyauto-vacuumcode).Donotwrite
**anysuchpagestothefile.
if(pList->pgno<=pPager->dbSize){
char*pData=CODEC2(pPager,PGHDR_TO_DATA(pList),pList->pgno,128)">6);
TRACE3(
STORE%dpage%d\n->pgno);
写入文件
rc=sqlite3OsWrite(pPager->fd,pData,pPager->pageSize);
TEST_INCR(pPager
->nWrite);
}
#ifndefNDEBUG
else{
TRACE3(
NOSTORE%dpage%d\n->pgno);
}
#endif
设置dirty
pList->dirty=0;
#ifdefsqlITE_CHECK_PAGES
pList
->pageHash=pager_pagehash(pList);
指向下一个脏页面
pList=pList->pDirty;
}
returnsqlITE_OK;
}

10、修改结果刷入存储设备(Flushing Changes To Mass Storage)
为了保证修改结果真正写入磁盘,这一步必不要少。对于数据库存的完整性,这一步也是关键的一步。由于要进行实际的I/O操作,所以和第7步一样,将花费较多的时间。

最后来看看这几步是如何实现的:

其实以上以上几步是在函数sqlite3BtreeSync()---btree.c中调用的(而关于该函数调用后面再讲)。

代码如下:


同步btree对应的数据库文件
函数返回之后,只需要提交写事务,删除日志文件intsqlite3BtreeSync(Btree*p,constchar*zMaster){
intrc=sqlITE_OK;
if(p->inTrans==TRANS_WRITE){
BtShared
*pBt=p->pBt;
PgnonTrunc
=0;
#ifndefsqlITE_OMIT_AUTOVACUUM
if(pBt->autoVacuum){
rc
=autoVacuumCommit(pBt,&nTrunc);
returnrc;
}
}
调用pager进行sync
rc=sqlite3pager_sync(pBt->pPager,zMaster,nTrunc);
}
把pager所有脏页面写回文件intsqlite3pager_sync(Pager*pPager,255)">char*zMaster,PgnonTrunc){
intrc=sqlITE_OK;

TRACE4(
DATABASESYNC:File=%szMaster=%snTrunc=%d\n pPager->zFilename,nTrunc);

Ifthisisanin-memorydb,ornopageshavebeenwrittento,orthis
**functionhasalreadybeencalled,itisano-op.
pager不处于PAGER_SYNCED状态,dirtyCache为1,
则进行sync操作if(pPager->state!=PAGER_SYNCED&&!MEMDB&&pPager->dirtyCache){
PgHdr
*pPg;
assert(pPager
->journalOpen);

Ifamasterjournalfilenamehasalreadybeenwrittentothe
**journalfile,thennosyncisrequired.Thishappenswhenitis
**written,thentheprocessfailstoupgradefromaRESERVEDtoan
**EXCLUSIVElock.Thenexttimetheprocesstriestocommitthe
**transactionthem-jnamewillhavealreadybeenwritten.
if(!pPager->setMaster){
pager修改计数
rc=pager_incr_changecounter(pPager);
if(rc!=sqlITE_OK)gotosync_exit;
#ifndefsqlITE_OMIT_AUTOVACUUM
if(nTrunc!=0){
Ifthistransactionhasmadethedatabasesmaller,thenallpages
**beingdiscardedbythetruncationmustbewrittentothejournal
**file.
*/
Pgnoi;
void*pPage;
intiSkip=PAGER_MJ_PGNO(pPager);
for(i=nTrunc+1;i<=pPager->origDbSize;i++){
if(!(pPager->aInJournal[i/8]&(1<<(i&7)))&&i!=iSkip){
rc
=sqlite3pager_get(pPager,i,&pPage);
gotosync_exit;
rc
=sqlite3pager_write(pPage);
sqlite3pager_unref(pPage);
gotosync_exit;
}
}
}
#endif
rc
=writeMasterJournal(pPager,zMaster);
gotosync_exit;

sync日志文件
rc=syncJournal(pPager);
gotosync_exit;
}

#ifndefsqlITE_OMIT_AUTOVACUUM
0){
rc
=sqlite3pager_truncate(pPager,nTrunc);
gotosync_exit;
}
Writealldirtypagestothedatabasefile*/
pPg
=pager_get_all_dirty_pages(pPager);


把所有脏页面写回操作系统文件
rc=pager_write_pagelist(pPg);
Syncthedatabasefile.sync数据库文件if(!pPager->noSync){
rc
=sqlite3OsSync(pPager->fd,128)">0);
}

pPager
->state=PAGER_SYNCED;
}
elseif(MEMDB&&nTrunc!= }

sync_exit:
returnrc;
}

下图可以进一步解释该过程:

原文链接:https://www.f2er.com/sqlite/200565.html

猜你在找的Sqlite相关文章