精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

sqlite wal 分析

數據庫
本文將介紹 wal 原理,并源碼剖析 checkpoint 過程,同時討論下 wal 使用中的一些注意點。由于 sqlite 的復雜性,會省略掉一些細節,重點放在核心流程和 wal 并發的實現。

sqlite 提供了一種 redo log 型事務實現,支持讀寫的并發,見 write-ahead log(https://sqlite.org/wal.html)。

一. 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 進一步了解,可以參考以下資料:

??https://zhuanlan.zhihu.com/p/35574452??

??https://developer.aliyun.com/article/1009683??

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, 詳細格式見:https://www.sqlite.org/fileformat2.html

圖片

關于寫

  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 中沒有成功提交的事務。?

圖片

關于讀

  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 可以被多個讀事務使用。

圖片

關于 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。

二. wal 實現?

wal 的實現大部分代碼集中在 wal.c 中,從 sqlite 的架構劃分應該主要算是 pager 層的實現。

https://www.sqlite.org/arch.html。wal 實現從邏輯上由 3 部分組成:

2.1 wal 和 wal-index 文件格式

文件格式定義,官方文檔見:

??https://www.sqlite.org/walformat.html??

??https://www.sqlite.org/fileformat2.html??

這一層細節比較多,主要是些二進制定義。核心是 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 實現,前面有介紹過, 后面并發控制部分會詳細舉例介紹。

具體實現可看:

寫入:https://github.com/sqlite/sqlite/blob/version-3.15.2/src/pager.c#L3077

讀取:https://github.com/sqlite/sqlite/blob/version-3.15.2/src/wal.c#L2593

2.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 鎖的分類和作用

官方介紹:https://www.sqlite.org/walformat.html

可看 2.3.1節 How the various locks are used

也可看下面簡化分析:

圖片

3.2 鎖的持有情況

數據庫的訪問,可以分為 3 類:讀、寫和checkpoint。事務對鎖的持有不總是在事務一開始就持有,后文為了簡化分析,會假設讀寫事務對鎖的持有在事務開始時是已知的,并且與事務同生命周期。實際在讀事務某些執行路徑上也可能會持有 write lock,這里專注主線邏輯。

圖片

3.3 鎖的應用

這部分可以和源碼分析部分參照起來看,是整個 wal 里面相對復雜的部分,重點,需要來回反復看。

commit transaction:表示已經提交但沒有 checkpoint 的事務,藍框中表示事務修改的頁面。

ongoing transition : 表示正在進行中的事務,同時也表示一個活躍的數據庫連接,藍線表示 read mark 的位置。

pgx.y: 表示 page 的頁號和版本。

圖片

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 文件頭部。

四. checkpoint 源碼分析

源碼對應 sqlite 3.15.2,通過直接調用 checkpoint 觀察整個過程。

??https://github.com/sqlite/sqlite/tree/version-3.15.2/src??

4.1 調用鏈路

圖片

4.2 sqlite3_wal_checkpoint_v2

??https://github.com/sqlite/sqlite/blob/version-3.15.2/src/main.c#L2065??

主要是加鎖和一些參數校驗。

4.3 sqlite3Checkpoint

??https://github.com/sqlite/sqlite/blob/version-3.15.2/src/main.c#L2146??

ndb 上循環 checkpoint,大多數時候只有一個 db 文件。

4.4 sqlite3BtreeCheckpoint

??https://github.com/sqlite/sqlite/blob/version-3.15.2/src/btree.c#L9472??

檢查 btree 是否 locked,也是前置檢查邏輯。

4.5 sqlite3PagerCheckpoint

??https://github.com/sqlite/sqlite/blob/version-3.15.2/src/pager.c#L7198??

也是前置的處理邏輯。不過有個和 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

??https://github.com/sqlite/sqlite/blob/version-3.15.2/src/wal.c#L3192??

int sqlite3WalCheckpoint(
Wal *pWal, /* Wal connection */
int eMode, /* PASSIVE, FULL, RESTART, or TRUNCATE */
int (*xBusy)(void*), /* Function to call when busy */
void *pBusyArg, /* Context argument for xBusyHandler */
int sync_flags, /* Flags to sync db file with (or 0) */
int nBuf, /* Size of temporary buffer */
u8 *zBuf, /* Temporary buffer to use */
int *pnLog, /* OUT: Number of frames in WAL */
int *pnCkpt /* OUT: Number of backfilled frames in WAL */
){
int rc; /* Return code */
int isChanged = 0; /* True if a new wal-index header is loaded */
int eMode2 = eMode; /* Mode to pass to walCheckpoint() */
int (*xBusy2)(void*) = xBusy; /* Busy handler for eMode2 */
assert( pWal->ckptLock==0 );
assert( pWal->writeLock==0 );
/* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked
** in the SQLITE_CHECKPOINT_PASSIVE mode. */
assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 );
if( pWal->readOnly ) return SQLITE_READONLY;
WALTRACE(("WAL%p: checkpoint begins\n", pWal));

/* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive
** "checkpoint" lock on the database file. */
// 獨占獲取WAL_CKPT_LOCK鎖
rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
if( rc ){
/* EVIDENCE-OF: R-10421-19736 If any other process is running a
** checkpoint operation at the same time, the lock cannot be obtained and
** SQLITE_BUSY is returned.
** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured,
** it will not be invoked in this case.
*/
testcase( rc==SQLITE_BUSY );
testcase( xBusy!=0 );
return rc;
}
pWal->ckptLock = 1;
/* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and
** TRUNCATE modes also obtain the exclusive "writer" lock on the database
** file.
**
** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained
** immediately, and a busy-handler is configured, it is invoked and the
** writer lock retried until either the busy-handler returns 0 or the
** lock is successfully obtained.
*/
// 非SQLITE_CHECKPOINT_PASSIVE時,獨占獲取WAL_WRITE_LOCK鎖,并進行busy retry
if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_WRITE_LOCK, 1);
if( rc==SQLITE_OK ){
pWal->writeLock = 1;
}else if( rc==SQLITE_BUSY ){
eMode2 = SQLITE_CHECKPOINT_PASSIVE;
xBusy2 = 0;
rc = SQLITE_OK;
}
}
//如果wal-index顯示db有變化,unfetch db文件,和主線邏輯關系不大
/* Read the wal-index header. */
if( rc==SQLITE_OK ){
rc = walIndexReadHdr(pWal, &isChanged);
if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){
sqlite3OsUnfetch(pWal->pDbFd, 0, 0);
}
}
/* Copy data from the log to the database file. */
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);
}
/* If no error occurred, set the output variables. */
if( rc==SQLITE_OK || rc==SQLITE_BUSY ){
if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame;
if( pnCkpt ) *pnCkpt = (int)(walCkptInfo(pWal)->nBackfill);
}
}
// release wal index,非主線邏輯
if( isChanged ){
/* If a new wal-index header was loaded before the checkpoint was
** performed, then the pager-cache associated with pWal is now
** out of date. So zero the cached wal-index header to ensure that
** next time the pager opens a snapshot on this database it knows that
** the cache needs to be reset.
*/
memset(&pWal->hdr, 0, sizeof(WalIndexHdr));
}
// 釋放鎖,返回
/* Release the locks. */
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

??https://github.com/sqlite/sqlite/blob/version-3.15.2/src/wal.c#L1724??

static int walCheckpoint(
Wal *pWal, /* Wal connection */
int eMode, /* One of PASSIVE, FULL or RESTART */
int (*xBusy)(void*), /* Function to call when busy */
void *pBusyArg, /* Context argument for xBusyHandler */
int sync_flags, /* Flags for OsSync() (or 0) */
u8 *zBuf /* Temporary buffer to use */
){
int rc = SQLITE_OK; /* Return code */
int szPage; /* Database page-size */
WalIterator *pIter = 0; /* Wal iterator context */
u32 iDbpage = 0; /* Next database page to write */
u32 iFrame = 0; /* Wal frame containing data for iDbpage */
u32 mxSafeFrame; /* Max frame that can be backfilled */
u32 mxPage; /* Max database page to write */
int i; /* Loop counter */
volatile WalCkptInfo *pInfo; /* The checkpoint status information */
szPage = walPagesize(pWal);
testcase( szPage<=32768 );
testcase( szPage>=65536 );
pInfo = walCkptInfo(pWal);
if( pInfo->nBackfill<pWal->hdr.mxFrame ){
/* Allocate the iterator */
rc = walIteratorInit(pWal, &pIter);
if( rc!=SQLITE_OK ){
return rc;
}
assert( pIter );
/* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked
** in the SQLITE_CHECKPOINT_PASSIVE mode. */
assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 );
/* Compute in mxSafeFrame the index of the last frame of the WAL that is
** safe to write into the database. Frames beyond mxSafeFrame might
** overwrite database pages that are in use by active readers and thus
** cannot be backfilled from the WAL.
*/
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-sanitizer reports that the following is an unsafe read,
** as some other thread may be in the process of updating the value
** of the aReadMark[] slot. The assumption here is that if that is
** happening, the other client may only be increasing the value,
** not decreasing it. So assuming either that either the "old" or
** "new" version of the value is read, and not some arbitrary value
** that would never be written by a real client, things are still
** safe. */
u32 y = 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);
}else if( rc==SQLITE_BUSY ){
// 一直沒有獲取對應WAL_READ_LOCK(i)鎖,設置mxSafeFrame為y
mxSafeFrame = y;
xBusy = 0;
}else{
goto walcheckpoint_out;
}
}
}
// 開始從wal文件寫回db文件,此時獨占的持有WAL_READ_LOCK(0)
if( pInfo->nBackfill<mxSafeFrame
&& (rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(0),1))==SQLITE_OK
){
i64 nSize; /* Current size of database file */
u32 nBackfill = pInfo->nBackfill;
pInfo->nBackfillAttempted = mxSafeFrame;
/* Sync the WAL to disk */
if( sync_flags ){
rc = sqlite3OsSync(pWal->pWalFd, sync_flags);
}
/* If the database may grow as a result of this checkpoint, hint
** about the eventual size of the db file to the VFS layer.
*/
if( rc==SQLITE_OK ){
i64 nReq = ((i64)mxPage * szPage);
rc = sqlite3OsFileSize(pWal->pDbFd, &nSize);
if( rc==SQLITE_OK && nSize<nReq ){
sqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq);
}
}
// 邏輯比較簡單,遍歷并回寫
/* Iterate through the contents of the WAL, copying data to the db file */
while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){
i64 iOffset;
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) ); // requires a 4GiB WAL file */
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;
}
/* If work was actually accomplished... */
if( rc==SQLITE_OK ){
if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){
i64 szDb = 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;
}
}
/* Release the reader lock held while backfilling */
// 釋放 WAL_READ_LOCK(0)
walUnlockExclusive(pWal, WAL_READ_LOCK(0), 1);
}

if( rc==SQLITE_BUSY ){
/* Reset the return code so as not to report a checkpoint failure
** just because there are active readers. */
rc = SQLITE_OK;
}
}
/* If this is an SQLITE_CHECKPOINT_RESTART or TRUNCATE operation, and the
** entire wal file has been copied into the database file, then block
** until all readers have finished using the wal file. This ensures that
** the next process to write to the database restarts the wal file.
*/
// 非passive的checkpoint的區別都在這里
if( rc==SQLITE_OK && eMode!=SQLITE_CHECKPOINT_PASSIVE ){
assert( pWal->writeLock );
if( pInfo->nBackfill<pWal->hdr.mxFrame ){
// 沒有全部checkpoint
rc = SQLITE_BUSY;
}else if( eMode>=SQLITE_CHECKPOINT_RESTART ){
// RESTART or TRUNCATE
u32 salt1;
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-57140 This mode works the same way as
** SQLITE_CHECKPOINT_RESTART with the addition that it also
** truncates the log file to zero bytes just prior to a
** successful return.
**
** In theory, it might be safe to do this without updating the
** wal-index header in shared memory, as all subsequent reader or
** writer clients should see that the entire log file has been
** checkpointed and behave accordingly. This seems unsafe though,
** as it would leave the system in a state where the contents of
** the wal-index header do not match the contents of the
** file-system. To avoid this, update the wal-index header to
** indicate that the log file contains zero valid frames. */
walRestartHdr(pWal, salt1);
// Truncate wal文件
rc = sqlite3OsTruncate(pWal->pWalFd, 0);
}
walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
}
}
}
walcheckpoint_out:
walIteratorFree(pIter);
return rc;
}

五. 常見問題

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 有機會完全提交,而不總是部分提交。
責任編輯:龐桂玉 來源: 字節跳動技術團隊
相關推薦

2019-09-26 08:40:27

SqliteTips優化

2009-03-04 09:11:20

類型親和性類型約束SQLite

2024-01-16 09:35:00

數據庫應用

2023-07-30 17:34:53

KV存儲ChunkPosit

2011-08-01 10:21:01

SQLite

2011-06-02 10:24:48

Android SQLite

2025-04-14 02:00:00

SQLiteRedisRedka

2012-03-06 09:13:04

Android SQLSQLiteAndroid

2021-06-30 18:16:38

MySQLWal策略

2013-07-25 14:44:48

sqlite實例教程iOS開發學習sqlite打造詞典

2022-09-19 09:03:08

命令壓縮優化

2011-07-04 17:18:23

Qt SQLite 數據庫

2021-02-15 15:40:28

SQLite3數據庫

2011-08-22 16:42:43

SqliteiPad

2011-07-05 10:22:44

Qt Sqlite

2022-08-04 11:10:03

日志優化

2021-07-01 11:56:04

etcd-wal模塊解析數據庫

2011-07-20 12:34:49

SQLite數據庫約束

2011-04-19 13:26:47

DB(BDB)SQLite

2019-08-15 07:00:54

SQLite數據庫內存數據庫
點贊
收藏

51CTO技術棧公眾號

久久99九九| 最近2019好看的中文字幕免费| 欧美在线观看黄| www香蕉视频| 香蕉成人久久| 尤物tv国产一区| 亚洲视频在线不卡| 蜜桃视频在线观看播放| 久久久久久久综合狠狠综合| 国产精品久久久久久久电影| 男女性高潮免费网站| 欧美日韩直播| 欧美日韩成人综合天天影院 | 大片网站久久| 精品日本一线二线三线不卡| 嫩草av久久伊人妇女超级a| 黄网站app在线观看| 成人免费视频视频在线观看免费 | 亚洲国产成人精品激情在线| 精品国产视频| 亚洲精品一区二区精华| 日本a√在线观看| hd国产人妖ts另类视频| 国产丝袜欧美中文另类| 国产精品国产精品国产专区不卡| 黑人一级大毛片| 91成人超碰| 亚洲人在线视频| 苍井空张开腿实干12次| 成人全视频免费观看在线看| 婷婷夜色潮精品综合在线| 日韩视频在线观看视频| 阿v免费在线观看| 99久久久无码国产精品| 999热视频| 国产偷人爽久久久久久老妇app | 香蕉视频免费看| 国产一区不卡视频| 国产精品日韩欧美大师| www.com国产| 精品福利电影| 欧美另类极品videosbestfree| 日本一级免费视频| 色婷婷狠狠五月综合天色拍| 精品国产一区二区在线观看| 两性午夜免费视频| 爱情电影网av一区二区| 欧美性受极品xxxx喷水| 黑森林福利视频导航| 草草在线观看| 亚洲成人一区二区在线观看| 日韩一二区视频| 成人短视频在线观看| 国产精品美女久久久久久久久 | 日韩av中文在线| 国产熟女高潮视频| 9lporm自拍视频区在线| 夜夜亚洲天天久久| 国产资源第一页| 自由的xxxx在线视频| 亚洲少妇30p| 免费观看黄色大片| 超碰caoporn久久| 亚洲精品美国一| 可以免费看的黄色网址| 麻豆影院在线| 亚洲色图20p| 红桃一区二区三区| av大大超碰在线| 一区二区三区国产豹纹内裤在线| 男人草女人视频| 黑人极品ⅴideos精品欧美棵| 亚洲一区二区三区视频在线播放 | 日本色护士高潮视频在线观看| 一区在线播放视频| 99热都是精品| 超碰在线网站| 午夜婷婷国产麻豆精品| 18岁网站在线观看| 精品欧美日韩精品| 欧美电影一区二区| 成人一区二区三区仙踪林| 精品午夜电影| 亚洲精品永久免费| 天堂av网手机版| 亚洲电影在线一区二区三区| 欧美激情18p| 中文字幕黄色片| 久久99久久99| 国产午夜精品一区| 狠狠v欧美ⅴ日韩v亚洲v大胸| 国产农村妇女精品| 黄色特一级视频| 国模冰冰炮一区二区| 欧美日韩成人在线一区| 一级黄色片毛片| 蜜桃一区二区| 不卡av日日日| 国产精品视频免费播放| 黄网站免费久久| 久久riav二区三区| 色开心亚洲综合| 午夜精品久久久久久久久久| 9久久婷婷国产综合精品性色 | 女同一区二区免费aⅴ| 精品国产91久久久久久老师| 国产精品视频分类| 国产成人一二| 日韩一区在线视频| 欧美啪啪小视频| 极品美女销魂一区二区三区| 九九九九九九精品| av网址在线| 日本韩国一区二区三区视频| 成人三级做爰av| 精品美女久久| 91国产精品视频在线| 91成人在线免费| 91色porny| 国产女主播自拍| 四虎精品在线观看| 亚洲色图狂野欧美| 五月婷婷激情网| 激情欧美一区二区| 日韩欧美一区二区三区久久婷婷| 黄色在线看片| 91精品一区二区三区久久久久久| 国产美女喷水视频| 激情久久久久久| 91影视免费在线观看| 国产三级视频在线看| 亚洲超碰精品一区二区| 国产农村妇女精品久久| 全球成人免费直播| 国产999在线观看| 婷婷丁香一区二区三区| 亚洲精品第1页| 久久久久xxxx| 成人系列视频| 国产精品91视频| 免费在线视频你懂得| 亚洲一级二级三级在线免费观看| 天堂视频免费看| 日韩精品一区二区三区免费观看| 欧洲日本亚洲国产区| 免费观看成年人视频| 一卡二卡欧美日韩| 日本在线视频播放| 欧美777四色影| 亚洲精品免费av| 1区2区3区在线视频| 91精品午夜视频| 亚洲国产精品免费在线观看| 奇米一区二区三区| 亚洲精品乱码久久久久久蜜桃91| 香蕉久久免费电影| 亚洲日韩第一页| 日韩黄色片网站| 国产喂奶挤奶一区二区三区| 欧美日韩第二页| 精品国产aⅴ| 国产精品网站入口| 国内精品不卡| 日韩欧美第一区| 国产一级在线观看视频| 成人动漫一区二区三区| 波多野结衣家庭教师在线| 亚洲小说图片视频| 国产精品www色诱视频| 超碰97在线免费观看| 欧美日韩成人一区二区| 加勒比婷婷色综合久久| 成人午夜在线播放| 黄色免费福利视频| 久久99国产精品视频| 国产精品日韩精品| 2024短剧网剧在线观看| 亚洲国产精品999| 少妇高潮av久久久久久| 中文字幕第一区第二区| 精品人妻一区二区三| 在线看片成人| 日本一区二区三区视频免费看| 黄页免费欧美| 欧美黑人巨大xxx极品| 色视频免费在线观看| 在线视频一区二区免费| 小早川怜子一区二区的演员表| 国产精品系列在线观看| 色欲av无码一区二区人妻| 成人综合久久| 国产精品精品软件视频| 欧美日韩国产网站| 欧美成人激情在线| 日韩精品一二| 91精品欧美一区二区三区综合在 | 99久久99久久精品国产片| 午夜激情在线播放| 久久精品欧美视频| 手机亚洲第一页| 欧美一区二区网站| 国产精品男女视频| 亚洲视频你懂的| 国产国语性生话播放| 美女国产一区二区三区| 无码av天堂一区二区三区| 国产精品一区二区99| 99热在线播放| 国产精品蜜月aⅴ在线| 97精品免费视频| 国产美女av在线| 亚洲视频在线免费观看| 黄色av网址在线| 4438x亚洲最大成人网| 波多野结衣 久久| 亚洲成av人影院| 日韩精品123区| 久久精品视频网| 天堂久久久久久| 国产91精品免费| 五月六月丁香婷婷| 日本中文字幕一区二区视频| 久草热视频在线观看| 亚洲欧美综合| 中文字幕在线观看一区二区三区| 深爱激情综合网| 久久久综合香蕉尹人综合网| 亚洲啊v在线免费视频| 亚洲va国产va天堂va久久| 成人免费网站www网站高清| 97超视频免费观看| 国内高清免费在线视频| 欧美精品在线免费| caoporn免费在线| 日韩有码片在线观看| av男人的天堂在线| 夜夜嗨av色一区二区不卡| 青青久草在线| 亚洲欧美日韩网| 三级黄视频在线观看| 欧美精品一区二区在线播放 | 1024视频在线| 亚洲视频免费一区| 天天插天天干天天操| 日韩欧美色综合网站| 亚洲一二区视频| 日韩欧美在线字幕| 国产污视频在线看| 亚洲午夜精品一区二区三区他趣| 国产中文字幕久久| 国产亚洲欧美激情| 99久久久无码国产精品衣服| 99精品欧美一区二区蜜桃免费 | 免费在线观看黄视频| 国产精品卡一卡二卡三| 精品视频第一页| 国产亚洲午夜高清国产拍精品 | 亚洲国产成人在线| 小早川怜子久久精品中文字幕| 99精品国产一区二区三区不卡| 无码精品一区二区三区在线播放| 国产成人一区在线| 四虎国产精品永久免费观看视频| 日韩黄色在线观看| 91小视频网站| 蜜臀av性久久久久蜜臀av麻豆| 看欧美ab黄色大片视频免费| 麻豆精品新av中文字幕| 色噜噜狠狠永久免费| 久久av资源站| 日本wwwwwww| 成人午夜私人影院| 你懂得在线视频| 国产精品女人毛片| 性色国产成人久久久精品| 国产精品久久福利| 麻豆疯狂做受xxxx高潮视频| 一区二区三区四区激情| 九九视频在线免费观看| 亚洲高清免费视频| 日本中文字幕免费观看| 亚洲综合在线视频| 日韩欧美不卡视频| 欧美无人高清视频在线观看| 中文字幕 日韩有码| 欧美老肥妇做.爰bbww| 丰满少妇一级片| 精品亚洲男同gayvideo网站| 黄上黄在线观看| 久久手机免费视频| free性欧美| 日韩美女免费视频| 国产一精品一av一免费爽爽| 国产成人成网站在线播放青青| 99久久香蕉| 亚洲精品在线免费| 欧美黄色一区| 国产精品50p| 国产剧情在线观看一区二区| 中文字幕第3页| 国产日韩欧美制服另类| 麻豆国产尤物av尤物在线观看| 欧美日韩午夜视频在线观看| 精品一区二三区| 在线综合亚洲欧美在线视频| 亚洲精品视频网| 亚洲欧美国产日韩天堂区| 亚乱亚乱亚洲乱妇| 久久久久久久一| 国产经典一区| 国产亚洲精品久久飘花| 日韩综合在线| 国产精品裸体瑜伽视频| 国产精品原创巨作av| 欧美bbbbb性bbbbb视频| 国产精品乱人伦中文| 国产主播在线播放| 91麻豆精品国产91久久久资源速度 | 黄色片久久久久| 成人av在线影院| 国产一区二区三区四区在线| 亚洲午夜一二三区视频| 国产女无套免费视频| 精品亚洲男同gayvideo网站| av在线下载| 国产在线拍偷自揄拍精品| 欧美网色网址| 日本精品福利视频| 美女脱光内衣内裤视频久久网站| 少妇激情一区二区三区视频| 中文字幕一区二区三区在线观看| 免费又黄又爽又猛大片午夜| 欧美va在线播放| av一本在线| 国产精品高潮在线| 久久99偷拍| 久久久成人精品一区二区三区| 免费久久99精品国产自在现线| 国产一级二级av| 玉米视频成人免费看| 伊人久久国产精品| 亚洲欧美变态国产另类| 中文字幕 在线观看| 成人av蜜桃| 正在播放日韩欧美一页| 国产精品无码专区av在线播放| 91麻豆精品在线观看| 日本网站免费观看| 亚洲国产成人精品女人久久久 | 五月婷婷开心网| 日韩免费视频一区| 麻豆传媒在线完整视频| 亚洲综合中文字幕在线| 国产高清一区| 特种兵之深入敌后| 自拍av一区二区三区| 91美女精品网站| 久久综合色影院| 成人av在线播放| 一区二区三区偷拍| 狠狠色丁香婷综合久久| 91制片厂在线| 欧美日本免费一区二区三区| 国产爆初菊在线观看免费视频网站 | 91久久国产综合久久蜜月精品 | 国产精品专区在线| 成人午夜精品一区二区三区| 一级片免费网址| 亚洲精品小视频在线观看| 九色porny视频在线观看| 狠狠综合久久av| 香蕉国产精品偷在线观看不卡| 久久成人激情视频| 欧美亚洲丝袜传媒另类| 91精品大全| 99精品国产一区二区| 亚洲国产精品第一区二区三区| 国产精品无码久久久久一区二区| 色婷婷久久综合| 成人高清免费观看mv| 91亚洲一区精品| 国模大胆一区二区三区| 国产一级二级av| 色天天综合久久久久综合片| 成人在线免费视频| 99视频国产精品免费观看| 91久久午夜| 波多野结衣一本| 日韩一区二区在线看片| 青草在线视频| 亚洲一区二区三区在线观看视频 | 国产成人精品视频在线| 成人系列视频| 欧美日韩一区二区区别是什么| 亚洲成人免费看| 国产福利电影在线| 91黄色精品| 亚洲毛片视频| 国产手机在线观看| 欧美一区二区精品|