sqlite 供给了一种 redo log 型业务完成,支撑读写的并发,见write-ahead log(sqlite.org/wal.html)。本… wal 原理,并源码剖析 checkpoint 进程,一起讨论下 wal 运用中的一些注意点。因为 sqlite 的杂乱性,会省掉掉一些细节,要点放在中心流程和 wal 并发的完成。

1. wal 原理

1.1 redo log

sqlite wal 是一种简单的 redo log 业务完成,redo log 概念这儿简述下。数据库业务需求满意满意 acid,其中原子性(a),即一次业务内的多个修正,要么悉数提交成功要么悉数提交失利,不存在部分提交到 db 的状况。 redo log 的处理思路是将修正后的日志按序先写入 log 文件(wal 文件),每个完结的业务会增加 checksum,可鉴别业务的完好性。业务写入日志文件后,即代表提交成功,读取时日志和 db 文件合并的成果构成了 db 的完好内容。一起定时 checkpoint,同步 wal 中的业务到 db 文件,使 wal 文件保持在合理的大小。日志文件耐久化到磁盘后,已提交成功的业务按序 checkpoint 履行的成果都是相同的,不受 crash 和掉电的影响。

sqlite 的 wal 也是这种思路的完成,仅仅 sqlite 供给的是一种简化完成,一起只允许一个写者操作日志文件,日志也是 page 这种物理日志。redo log 还能将 undo log 的随机写转化为顺序写,具有更高的写入性能, 这儿不赘述。

想对 redo log 进一步了解,能够参考以下资料:

zhuanlan.zhihu.com/p/35574452

developer.aliyun.com/article/100…

1.2 sqlite wal

sqlite wal 写操作不直接写入 db 主文件,而是写到“db-wal”文件(以下简称’wal’文件)的末尾。读操作时,将结合 db 主文件以及 wal 的内容回来成果。wal 形式一起具有简单的 mvvc 完成,支撑文件等级的读写并发,供给了相对 delete(rollback) 形式 (undo log 业务) 更高的并发性。 具体可看图加深理解。

下图中:

  1. pgx.y,x 表明当时 page 的 num,y 表明当时 page 的版别,每个提交的业务都保存当时修正后的 page 副本;
  2. 图中 wal 中提交了两个业务,wal 中蓝色框表明一个完好业务修正的一切 page;
  3. wal 实际中保存的单位是 wal frame,除了修正的页面还会保存 page number checksum 等信息,这儿为了杰出展现了 page, 具体格局见:www.sqlite.org/fileformat2…

sqlite wal 分析

关于写

  1. 写操作总是发生在 wal 文件上;
  2. 写操作总是追加在 wal 文件末尾,由 commit 触发;
  3. 写入 wal 文件中是原始 page 修正后的副本;
  4. 写操刁难 wal 文件的拜访是独占串行的;
  5. 业务写入只要成功落盘(写入磁盘)才算成功提交,checkpoint 前会调用 wal 文件的 fsync,确保日志提交耐久性和一致性;
  6. 没有调用 fsync 不代表日志提交一定失利,会由文件系统定时回写;
  7. 假如 fsync 回写之前发生 crash 或系统溃散,导致业务 2 的 pg4.2 写 wal 失利,可校验出业务 2 不完好,则 wal 中成功提交的业务只要业务 1; 假如 pg0.1 回写失利,则 wal 中没有成功提交的业务。

sqlite wal 分析

关于读
  1. 读与写能够并发;

  2. 每个读业务会记载 wal 文件中一个 record 点,作为它的 read mark,每个业务履行进程中 read mark 不会发生改变,新提交的业务发生的修正不会影响旧的业务。read mark 会挑选业务完好提交后的方位。原始 db 文件和 wal 中 read mark 之前的记载构成了数据库的一个固定的版别记载;

  3. 读业务读一个 page 优先读 wal 文件,没有则读原始文件;

  4. 假如一个 page 在 wal 中有多个副本,读 read mark 前的最终一个;

  5. 同一个 read mark 能够被多个读业务运用。

sqlite wal 分析

关于checkpoint:

  1. checkpoint 针对 wal 中现已成功落盘的业务,每次 checkpoint 前会履行 fsync;

  2. 每次 checkpoint 从前到后按序回写 wal 文件中没有提交的业务到 db;

  3. 假如 checkpoint 中途 crash,因为业务已耐久化到 wal 文件,下次发动从头按序回写 wal 中的业务即可;

  4. wal 中一切的业务 checkpoint 后,wal 文件会从头开端运用;

  5. checkpoint 并不一定都会提交 wal 中悉数的业务,假如仅仅部分提交,下次写入仍是会写入 wal 文件的末尾,wal 文件或许会变很大;

  6. 只要 truncate 的 checkpoint 才能清理现已反常变大的 wal 文件,会 truncate 文件大小到 0。

2. wal 完成

wal 的完成大部分代码会集在 wal.c 中,从 sqlite 的架构划分应该首要算是 pager 层的完成。

www.sqlite.org/arch.html。w… 完成从逻辑上由 3 部分组成:

2.1 wal 和 wal-index 文件格局

文件格局界说,官方文档见:

www.sqlite.org/walformat.h…

www.sqlite.org/fileformat2…

这一层细节比较多,首要是些二进制界说。中心是 wal 格局供给了一种 page 格局的 redo log 安排格局,确保 crash 后 recover 进程满意一致性。

wal-index 文件(db-shm)仅仅一种对 wal 文件的快速索引,后文为了省劲,也统称 wal 文件。

2.2 文件多副本笼统

即 wal 和 db 文件对外表现为一个一致的文件笼统,并供给文件等级的 mvcc,对 pager 层屏蔽 wal 细节。

因为 wal 和 db 相同都是以 pgno 的方式索引 page,按 pgno 替换就能够构造出不同版别的 b 树,比较简单。mvcc 首要经过 read lock 的 read mark 完成,前面有介绍过, 后面并发操控部分会具体举例介绍。

具体完成可看:

写入:github.com/sqlite/sqli…

读取:github.com/sqlite/sqli…

2.3 并发操控

经过文件锁确保并发操作不会损坏数据库文件,下一节具体解说。

3. wal 下的并发

wal 支撑读读、读写的并发,相比最初的 rollback journal 形式供给了更大的并发力度。 但 wal 完成的是文件等级的并发,没有 mysql 表锁行锁的概念,一个 db 文件一起的并发写业务一起只能存在一个,不支撑写的一起并发。checkpoint 也或许会 block 读写。

wal 并发完成上首要经过文件锁,和文件等级 mvcc 来完成文件等级的读写并发。 锁即下文源码中的 WAL_CKPT_LOCK,WAL_WRITE_LOCK 和WAL_READ_LOCK,出于简化问题考虑省掉了 WAL_RECOVER_LOCK 等相关性不大的其他锁的讨论。mvcc 即经过文件多副本和 read mark 完成,后文也会具体介绍。

3.1 锁的分类和效果

官方介绍:www.sqlite.org/walformat.h…

可看2.3.1节 How the various locks are used

也可看下面简化剖析:

sqlite wal 分析

3.2 锁的持有状况

数据库的拜访,能够分为 3 类:读、写和checkpoint。业务对锁的持有不总是在业务一开端就持有,后文为了简化剖析,会假定读写业务对锁的持有在业务开端时是已知的,并且与业务同生命周期。实际在读业务某些履行途径上也或许会持有 write lock,这儿专注主线逻辑。

sqlite wal 分析

3.3 锁的应用

这部分能够和源码剖析部分参照起来看,是整个 wal 里面相对杂乱的部分,要点,需求来回反复看。

commit transaction:表明现已提交但没有 checkpoint 的业务,蓝框中表明业务修正的页面。

ongoing transition : 表明正在进行中的业务,一起也表明一个活跃的数据库衔接,蓝线表明 read mark 的方位。

pgx.y: 表明 page 的页号和版别。

sqlite wal 分析

3.3.1 读写

如图可知:

  1. wal文件存在 4 个现已提交的业务

    第一个业务修正了 page0,第二个业务修正了 page0、1、3,依此类推。

  2. 当时数据库上存在 4 个活跃的衔接,包括 3 个读业务和 1 个写业务;

  3. 写业务独占了 WAL_WRITE_LOCK,所以此刻不能再建议一个写业务;

  4. 写业务占有 1(4)读锁,所以写业务读取不到 read mark 4 之后的修正,只能读取 read mark 4 之前的修正。即写业务读取 page4 时不能读取到 page4.3,只能读取 page4.0;

  5. 3 个读业务占有 0(0),1(4),2(5)三个读锁,read mark 只能在业务完毕的方位,不会处于中间 page 的方位;

  6. 后续假如建议一个读业务,会占有读锁 3(7)。理论上能够建议任意多个读请求,读锁能够被 sqlite 衔接同享。

3.3.2 checkpoint

这部分要和源码剖析结合, 假如此刻建议 checkpoint。

  1. 因为业务 0 持有 read lock 0,read mark 0,计算 mxSafeFrame 为 0,不会发生 checkpoint。

    假如业务 0 完毕后建议 checkpoint。

  2. 因为写业务存在,不能建议非 passive 的 checkpoint。

    假如业务 1 完毕后履行 checkpoint。

  3. 计算 mxSafeFrame 等于 4,会提交前 4 个 page,没有彻底提交,wal 文件不会从头利用,新的写入仍是会写入 commit transaction3 之后。

    假如一切业务完毕后履行 checkpoint。

  4. 提交一切页面,下次写入 wal 文件头部。

4. checkpoint 源码剖析

源码对应 sqlite 3.15.2,经过直接调用 checkpoint 观察整个进程。

github.com/sqlite/sqli…

4.1 调用链路

sqlite wal 分析

4.2 sqlite3_wal_checkpoint_v2

github.com/sqlite/sqli…

首要是加锁和一些参数校验。

4.3 sqlite3Checkpoint

github.com/sqlite/sqli…

ndb 上循环 checkpoint,大多数时分只要一个 db 文件。

4.4 sqlite3BtreeCheckpoint

github.com/sqlite/sqli…

查看 btree 是否 locked,也是前置查看逻辑。

4.5 sqlite3PagerCheckpoint

github.com/sqlite/sqli…

也是前置的处理逻辑。不过有个和 checkpoint 逻辑有关的。

/*只在非SQLITE_CHECKPOINT_PASSIVE形式时设置xBusyHandler
*即SQLITE_CHECKPOINT_PASSIVE时假如获取不到锁,立即回来,不进行等候并retry
*/
if(pPager->pWal){
rc=sqlite3WalCheckpoint(pPager->pWal,db,eMode,
(eMode==SQLITE_CHECKPOINT_PASSIVE?0:pPager->xBusyHandler),
pPager->pBusyHandlerArg,
pPager->walSyncFlags,pPager->pageSize,(u8*)pPager->pTmpSpace,
pnLog,pnCkpt
);
}

4.6 sqlite3WalCheckpoint

github.com/sqlite/sqli…

intsqlite3WalCheckpoint(
Wal*pWal,/*Walconnection*/
inteMode,/*PASSIVE,FULL,RESTART,orTRUNCATE*/
int(*xBusy)(void*),/*Functiontocallwhenbusy*/
void*pBusyArg,/*ContextargumentforxBusyHandler*/
intsync_flags,/*Flagstosyncdbfilewith(or0)*/
intnBuf,/*Sizeoftemporarybuffer*/
u8*zBuf,/*Temporarybuffertouse*/
int*pnLog,/*OUT:NumberofframesinWAL*/
int*pnCkpt/*OUT:NumberofbackfilledframesinWAL*/
){
intrc;/*Returncode*/
intisChanged=0;/*Trueifanewwal-indexheaderisloaded*/
inteMode2=eMode;/*ModetopasstowalCheckpoint()*/
int(*xBusy2)(void*)=xBusy;/*BusyhandlerforeMode2*/
assert(pWal->ckptLock==0);
assert(pWal->writeLock==0);
/*EVIDENCE-OF:R-62920-47450Thebusy-handlercallbackisneverinvoked
**intheSQLITE_CHECKPOINT_PASSIVEmode.*/
assert(eMode!=SQLITE_CHECKPOINT_PASSIVE||xBusy==0);
if(pWal->readOnly)returnSQLITE_READONLY;
WALTRACE(("WAL%p:checkpointbegins\n",pWal));
/*IMPLEMENTATION-OF:R-62028-47212Allcallsobtainanexclusive
**"checkpoint"lockonthedatabasefile.*/
//独占获取WAL_CKPT_LOCK锁
rc=walLockExclusive(pWal,WAL_CKPT_LOCK,1);
if(rc){
/*EVIDENCE-OF:R-10421-19736Ifanyotherprocessisrunninga
**checkpointoperationatthesametime,thelockcannotbeobtainedand
**SQLITE_BUSYisreturned.
**EVIDENCE-OF:R-53820-33897Evenifthereisabusy-handlerconfigured,
**itwillnotbeinvokedinthiscase.
*/
testcase(rc==SQLITE_BUSY);
testcase(xBusy!=0);
returnrc;
}
pWal->ckptLock=1;
/*IMPLEMENTATION-OF:R-59782-36818TheSQLITE_CHECKPOINT_FULL,RESTARTand
**TRUNCATEmodesalsoobtaintheexclusive"writer"lockonthedatabase
**file.
**
**EVIDENCE-OF:R-60642-04082Ifthewriterlockcannotbeobtained
**immediately,andabusy-handlerisconfigured,itisinvokedandthe
**writerlockretrieduntileitherthebusy-handlerreturns0orthe
**lockissuccessfullyobtained.
*/
//非SQLITE_CHECKPOINT_PASSIVE时,独占获取WAL_WRITE_LOCK锁,并进行busyretry
if(eMode!=SQLITE_CHECKPOINT_PASSIVE){
rc=walBusyLock(pWal,xBusy,pBusyArg,WAL_WRITE_LOCK,1);
if(rc==SQLITE_OK){
pWal->writeLock=1;
}elseif(rc==SQLITE_BUSY){
eMode2=SQLITE_CHECKPOINT_PASSIVE;
xBusy2=0;
rc=SQLITE_OK;
}
}

//假如wal-index显示db有变化,unfetchdb文件,和主线逻辑关系不大
/*Readthewal-indexheader.*/
if(rc==SQLITE_OK){
rc=walIndexReadHdr(pWal,&isChanged);
if(isChanged&&pWal->pDbFd->pMethods->iVersion>=3){
sqlite3OsUnfetch(pWal->pDbFd,0,0);
}
}
/*Copydatafromthelogtothedatabasefile.*/
if(rc==SQLITE_OK){
if(pWal->hdr.mxFrame&&walPagesize(pWal)!=nBuf){
rc=SQLITE_CORRUPT_BKPT;
}else{
//checkpoint
rc=walCheckpoint(pWal,eMode2,xBusy2,pBusyArg,sync_flags,zBuf);
}
/*Ifnoerroroccurred,settheoutputvariables.*/
if(rc==SQLITE_OK||rc==SQLITE_BUSY){
if(pnLog)*pnLog=(int)pWal->hdr.mxFrame;
if(pnCkpt)*pnCkpt=(int)(walCkptInfo(pWal)->nBackfill);
}
}
//releasewalindex,非主线逻辑
if(isChanged){
/*Ifanewwal-indexheaderwasloadedbeforethecheckpointwas
**performed,thenthepager-cacheassociatedwithpWalisnow
**outofdate.Sozerothecachedwal-indexheadertoensurethat
**nexttimethepageropensasnapshotonthisdatabaseitknowsthat
**thecacheneedstobereset.
*/
memset(&pWal->hdr,0,sizeof(WalIndexHdr));
}
//开释锁,回来
/*Releasethelocks.*/
sqlite3WalEndWriteTransaction(pWal);
walUnlockExclusive(pWal,WAL_CKPT_LOCK,1);
pWal->ckptLock=0;
WALTRACE(("WAL%p:checkpoint%s\n",pWal,rc?"failed":"ok"));
return(rc==SQLITE_OK&&eMode!=eMode2?SQLITE_BUSY:rc);
}

4.7 walCheckpoint

github.com/sqlite/sqli…

staticintwalCheckpoint(
Wal*pWal,/*Walconnection*/
inteMode,/*OneofPASSIVE,FULLorRESTART*/
int(*xBusy)(void*),/*Functiontocallwhenbusy*/
void*pBusyArg,/*ContextargumentforxBusyHandler*/
intsync_flags,/*FlagsforOsSync()(or0)*/
u8*zBuf/*Temporarybuffertouse*/
){
intrc=SQLITE_OK;/*Returncode*/
intszPage;/*Databasepage-size*/
WalIterator*pIter=0;/*Waliteratorcontext*/
u32iDbpage=0;/*Nextdatabasepagetowrite*/
u32iFrame=0;/*WalframecontainingdataforiDbpage*/
u32mxSafeFrame;/*Maxframethatcanbebackfilled*/
u32mxPage;/*Maxdatabasepagetowrite*/
inti;/*Loopcounter*/
volatileWalCkptInfo*pInfo;/*Thecheckpointstatusinformation*/
szPage=walPagesize(pWal);
testcase(szPage<=32768);
testcase(szPage>=65536);
pInfo=walCkptInfo(pWal);
if(pInfo->nBackfill<pWal->hdr.mxFrame){
/*Allocatetheiterator*/
rc=walIteratorInit(pWal,&pIter);
if(rc!=SQLITE_OK){
returnrc;
}
assert(pIter);
/*EVIDENCE-OF:R-62920-47450Thebusy-handlercallbackisneverinvoked
**intheSQLITE_CHECKPOINT_PASSIVEmode.*/
assert(eMode!=SQLITE_CHECKPOINT_PASSIVE||xBusy==0);
/*ComputeinmxSafeFrametheindexofthelastframeoftheWALthatis
**safetowriteintothedatabase.FramesbeyondmxSafeFramemight
**overwritedatabasepagesthatareinusebyactivereadersandthus
**cannotbebackfilledfromtheWAL.
*/
mxSafeFrame=pWal->hdr.mxFrame;
mxPage=pWal->hdr.nPage;
/*计算mxSafeFrame
*会测验独占的获取aReadMark锁,假如获取到,则代表原先持有对应aReadMark锁的业务现已完毕。
*会不断的用busy rerty逻辑等候对应的读锁开释。
*假如对应事物一向没有开释aReadMark锁,最终的mxSafeFrame=MIN(unfinished_aReadMarks)
*/
for(i=1;i<WAL_NREADER;i++){
/*Thread-sanitizerreportsthatthefollowingisanunsaferead,
**assomeotherthreadmaybeintheprocessofupdatingthevalue
**oftheaReadMark[]slot.Theassumptionhereisthatifthatis
**happening,theotherclientmayonlybeincreasingthevalue,
**notdecreasingit.Soassumingeitherthateitherthe"old"or
**"new"versionofthevalueisread,andnotsomearbitraryvalue
**thatwouldneverbewrittenbyarealclient,thingsarestill
**safe.*/
u32y=pInfo->aReadMark[i];
if(mxSafeFrame>y){
assert(y<=pWal->hdr.mxFrame);
//测验获取WAL_READ_LOCK(i)锁,并进行忙等候
rc=walBusyLock(pWal,xBusy,pBusyArg,WAL_READ_LOCK(i),1);
if(rc==SQLITE_OK){
//成功获取 WAL_READ_LOCK(i)锁,设置为READMARK_NOT_USED;i==1,是个treak,不影响主流程
pInfo->aReadMark[i]=(i==1?mxSafeFrame:READMARK_NOT_USED);
walUnlockExclusive(pWal,WAL_READ_LOCK(i),1);
}elseif(rc==SQLITE_BUSY){
//一向没有获取对应WAL_READ_LOCK(i)锁,设置mxSafeFrame为y
mxSafeFrame=y;
xBusy=0;
}else{
gotowalcheckpoint_out;
}
}
}
//开端从wal文件写回db文件,此刻独占的持有WAL_READ_LOCK(0)
if(pInfo->nBackfill<mxSafeFrame
&&(rc=walBusyLock(pWal,xBusy,pBusyArg,WAL_READ_LOCK(0),1))==SQLITE_OK
){
i64nSize;/*Currentsizeofdatabasefile*/
u32nBackfill=pInfo->nBackfill;
pInfo->nBackfillAttempted=mxSafeFrame;
/*SynctheWALtodisk*/
if(sync_flags){
rc=sqlite3OsSync(pWal->pWalFd,sync_flags);
}
/*Ifthedatabasemaygrowasaresultofthischeckpoint,hint
**abouttheeventualsizeofthedbfiletotheVFSlayer.
*/
if(rc==SQLITE_OK){
i64nReq=((i64)mxPage*szPage);
rc=sqlite3OsFileSize(pWal->pDbFd,&nSize);
if(rc==SQLITE_OK&&nSize<nReq){
sqlite3OsFileControlHint(pWal->pDbFd,SQLITE_FCNTL_SIZE_HINT,&nReq);
}
}
//逻辑比较简单,遍历并回写
/*IteratethroughthecontentsoftheWAL,copyingdatatothedbfile*/
while(rc==SQLITE_OK&&0==walIteratorNext(pIter,&iDbpage,&iFrame)){
i64iOffset;
assert(walFramePgno(pWal,iFrame)==iDbpage);
if(iFrame<=nBackfill||iFrame>mxSafeFrame||iDbpage>mxPage){
continue;
}
iOffset=walFrameOffset(iFrame,szPage)+WAL_FRAME_HDRSIZE;
/*testcase(IS_BIG_INT(iOffset));//requiresa4GiBWALfile*/
rc=sqlite3OsRead(pWal->pWalFd,zBuf,szPage,iOffset);
if(rc!=SQLITE_OK)break;
iOffset=(iDbpage-1)*(i64)szPage;
testcase(IS_BIG_INT(iOffset));
rc=sqlite3OsWrite(pWal->pDbFd,zBuf,szPage,iOffset);
if(rc!=SQLITE_OK)break;
}
/*Ifworkwasactuallyaccomplished...*/
if(rc==SQLITE_OK){
if(mxSafeFrame==walIndexHdr(pWal)->mxFrame){
i64szDb=pWal->hdr.nPage*(i64)szPage;
testcase(IS_BIG_INT(szDb));
rc=sqlite3OsTruncate(pWal->pDbFd,szDb);
if(rc==SQLITE_OK&&sync_flags){
rc=sqlite3OsSync(pWal->pDbFd,sync_flags);
}
}
if(rc==SQLITE_OK){
/*更新nBackfill为现已checkpoint的部分
*nBackfill记载当时现已checkpoint的部分
*/
pInfo->nBackfill=mxSafeFrame;
}
}
/*Releasethereaderlockheldwhilebackfilling*/
//开释WAL_READ_LOCK(0)
walUnlockExclusive(pWal,WAL_READ_LOCK(0),1);
}
if(rc==SQLITE_BUSY){
/*Resetthereturncodesoasnottoreportacheckpointfailure
**justbecausethereareactivereaders.*/
rc=SQLITE_OK;
}
}
/*IfthisisanSQLITE_CHECKPOINT_RESTARTorTRUNCATEoperation,andthe
**entirewalfilehasbeencopiedintothedatabasefile,thenblock
**untilallreadershavefinishedusingthewalfile.Thisensuresthat
**thenextprocesstowritetothedatabaserestartsthewalfile.
*/
//非passive的checkpoint的区别都在这儿
if(rc==SQLITE_OK&&eMode!=SQLITE_CHECKPOINT_PASSIVE){
assert(pWal->writeLock);
if(pInfo->nBackfill<pWal->hdr.mxFrame){
//没有悉数checkpoint
rc=SQLITE_BUSY;
}elseif(eMode>=SQLITE_CHECKPOINT_RESTART){
//RESTARTorTRUNCATE
u32salt1;
sqlite3_randomness(4,&salt1);
assert(pInfo->nBackfill==pWal->hdr.mxFrame);
//获取一切读锁,确保下一个事物能够从头开端restart,即循环利用wal文件
rc=walBusyLock(pWal,xBusy,pBusyArg,WAL_READ_LOCK(1),WAL_NREADER-1);
if(rc==SQLITE_OK){
if(eMode==SQLITE_CHECKPOINT_TRUNCATE){
/*IMPLEMENTATION-OF:R-44699-57140Thismodeworksthesamewayas
**SQLITE_CHECKPOINT_RESTARTwiththeadditionthatitalso
**truncatesthelogfiletozerobytesjustpriortoa
**successfulreturn.
**
**Intheory,itmightbesafetodothiswithoutupdatingthe
**wal-indexheaderinsharedmemory,asallsubsequentreaderor
**writerclientsshouldseethattheentirelogfilehasbeen
**checkpointedandbehaveaccordingly.Thisseemsunsafethough,
**asitwouldleavethesysteminastatewherethecontentsof
**thewal-indexheaderdonotmatchthecontentsofthe
**file-system.Toavoidthis,updatethewal-indexheaderto
**indicatethatthelogfilecontainszerovalidframes.*/
walRestartHdr(pWal,salt1);
//Truncatewal文件
rc=sqlite3OsTruncate(pWal->pWalFd,0);
}
walUnlockExclusive(pWal,WAL_READ_LOCK(1),WAL_NREADER-1);
}
}
}
walcheckpoint_out:
walIteratorFree(pIter);
returnrc;
}

5. 常见问题

5.1 checkpoint 何时触发

  1. 手动调用 checkpoint 触发;
  2. 经过 sql 句子 PRAGMA wal_checkpoint 触发;
  3. sqlite 官方默认的 checkpoint 阈值是 1000 page,即当 wal 文件到达 1000 page 大小时,写操作的线程在完结写操作后同步进行 checkpoint 操作;
  4. 当最终一个衔接 close 时触发。

5.2 checkpoint 四种 mode 的区别

  1. passive 不会加写锁,也就是不会 block 写操作;
  2. 其他三种 mode 在回写 db 完毕之前的逻辑都是相同。区别是 restart 会测验再次独占获取读锁,确保 restart 型的 checkpoint 正常完毕后,下一个建议的业务会从头开端循环利用 wal 文件。truncate 形式更近一步会 truncate wal 文件。

5.3 wal 下读写和 checkpoint 的并发性

可看看上面不同操刁难锁的持有状况:

  1. 读和读能够一起进行;
  2. 读和写能够一起进行;
  3. checkpoint 和读业务也存在很大程度的并发,checkpoint 对读锁持有都是间歇性的,理论上都是耗时很短。仔细观察上面的源码剖析部分,虽然会周期性持有读锁,基本上是等候读业务开释读锁,在真正耗时的 io 操作回写 wal 日志到 db 的进程中,仍是能够建议读业务的。 这种完成 checkpoint 对读存在着某种避让,读操作过于激进,会导致 checkpoint 饥饿,极端点会导致 wal 文件反常大;
  4. passive checkpoint 和写业务,理论上也是能够并发;
  5. 非passive checkpoint 和写业务,理论上不能够并发。

5.4 wal 文件巨大的原因 & 怎么处理

5.4.1 原因

wal 文件供给的操作模型非常简单,只要在一次完好的 checkpoint 后才会重头开端循环利用 wal 文件,假如 checkpoint 一向没有提交当时的 wal 文件中一切更新,会导致 wal 文件无限增大。一起只要在 truncate 形式 checkpoint 才会缩减 wal 文件。

大概有以下原因会导致 wal 不能彻底提交,中心都是 checkpoint 竞赛不到锁。

  1. 非 passive 形式 checkpoint,需求获取 write lock,但获取不到;
  2. passive 形式 checkpoint 进程中,有并发的写操作,导致 wal 中有未提交的日志;
  3. checkpoint 没能及时获取所以读锁。

在 checkpoint 中不能如预料中的获得锁,首要有两种或许:

  1. 业务耗时很长,导致锁迟迟不能开释;
  2. 数据衔接中存在锁丢掉的状况,导致 checkpoint 永久不能获取到需求的锁;
  3. 数据库衔接过多,导致 checkpoint 进程中竞赛不到锁。

5.4.2 处理方案

综上要处理 wal 无限增大首要有:

  1. 尽量把无关代码移除业务,确保业务只做数据库相关的操作;
  2. 查看代码,避免出现锁丢掉的状况;
  3. 读写操作恰当退避,确保 checkpoint 有时机彻底提交,而不总是部分提交。