sqlite wal 分析

news2024/11/16 16:56:07

动手点关注

117506996e440005e7b035f116eec580.gif

干货不迷路

sqlite 提供了一种 redo log 型事务实现,支持读写的并发,见 write-ahead log(https://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 进一步了解,可以参考以下资料:

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

7c9e20e8b0042c6b8418ae8b0ca76e19.png

关于写

  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 中没有成功提交的事务。

222915526758fb06372df900246656a3.png关于读

  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 可以被多个读事务使用。

05e39a378802df0f72b41008ced8d272.png

关于 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 层的实现。

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 并发控制

通过文件锁保证并发操作不会损坏数据库文件,下一节详细讲解。

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

也可看下面简化分析:

e816247d89e0009829f61697f3b5897d.png

3.2 锁的持有情况

数据库的访问,可以分为 3 类:读、写和checkpoint。事务对锁的持有不总是在事务一开始就持有,后文为了简化分析,会假设读写事务对锁的持有在事务开始时是已知的,并且与事务同生命周期。实际在读事务某些执行路径上也可能会持有 write lock,这里专注主线逻辑。

0dfc4e902a1b9f216bf1d2dca6bb34be.png

3.3 锁的应用

这部分可以和源码分析部分参照起来看,是整个 wal 里面相对复杂的部分,重点,需要来回反复看。

commit transaction:表示已经提交但没有 checkpoint 的事务,蓝框中表示事务修改的页面。

ongoing transition : 表示正在进行中的事务,同时也表示一个活跃的数据库连接,蓝线表示 read mark 的位置。

pgx.y: 表示 page 的页号和版本。

016ba73d48aff1d754a85e01097d6268.png

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 观察整个过程。

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

4.1 调用链路

a2f5f143f475b6b1894cc28d2536d77e.png

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. 常见问题

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 有机会完全提交,而不总是部分提交。

aec1bba1c4666600b46f8fdcc830cdde.png

98a258cab5bd602eba792fa7430026dd.gif

● 火山引擎 DataLeap 的 Data Catalog 系统公有云实践

● 头条稳定性治理:ARC 环境中对 Objective-C 对象赋值的 Crash 隐患

● 字节跳动 YARN 云原生化演进实践

● 火山引擎云原生大数据在金融行业的实践

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/130453.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

知行之桥EDI系统如何通过ZIP端口压缩文件?

在EDI项目当中&#xff0c;对于IT技术不够成熟或设备不够完善的用户来说&#xff0c;EXCEL方案是较为适中的选择。收到合作伙伴发来的850订单之后&#xff0c;将订单数据转换为EXCEL&#xff0c;再将EXCEL发送至用户指定的邮箱。若每条订单单独发送至邮箱&#xff0c;当订单量大…

#榜样的力量#《新冠战“疫”——2022中国数据智能产业最具社会责任感企业》榜正式发布...

‍数据猿出品数据猿此次推出的#榜样的力量#《新冠战“疫”——2022中国数据智能产业最具社会责任感企业》榜单/奖项为公益主题策划活动。旨在为正能量助威&#xff0c;为中国数据智能产业中具有社会责任感的典型性企业发声。数据智能产业创新服务媒体——聚焦数智 改变商业新冠…

66、【链表】leetcode——142. 环形链表 II(C++、Python版本)

题目描述 方法一&#xff1a;哈希表 C版本 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(NULL) {}* };*/ class Solution { public:ListNode *detectCycle(ListNode *head) {unord…

年终小结:苟住

大家好&#xff0c;我是一哥&#xff0c;新的一年又要到来了&#xff0c;一个词总结下今年的状态 01 工作 2022真的比较难&#xff0c;作为互联网技能从业人员&#xff0c;相信大家一定经历了很多。这一年虽然我没有被毕业&#xff0c;但也经历了公司的业务调整&#xff0c;曾…

C51 --串口通信

3.2.1 串口的连接方式 RXD&#xff1a;数据输入引脚&#xff0c;接受数据&#xff1b;STC89系列对应P3.0口&#xff0c;上官一号有单独引出 TXD&#xff1a; 数据发送引脚&#xff0c;数据发送&#xff1b;STC89系列对应P3.0口&#xff0c;上官一号有单独引出 接线方式&…

2022 IoTDB Summit:IoTDB PMC 田原《大规模并行处理与边缘计算在 Apache IoTDB 中的实践》...

12 月 3 日、4日&#xff0c;2022 Apache IoTDB 物联网生态大会在线上圆满落幕。大会上发布 Apache IoTDB 的分布式 1.0 版本&#xff0c;并分享 Apache IoTDB 实现的数据管理技术与物联网场景实践案例&#xff0c;深入探讨了 Apache IoTDB 与物联网企业如何共建活跃生态&#…

带头双向循环链表的增删改查

目录&#x1f921;前言&#x1f60a;带头双向循环链表&#x1f44d;链表实现&#x1f620;插入-头插&#xff0c;尾插&#xff0c;随机插✅删除-头删&#xff0c;尾删&#xff0c;随机删&#x1f602;链表搜索和链表修改&#x1f4a1;总结&#x1f921;前言 本篇博客主要是介绍…

Qt编写雷达模拟仿真工具(模拟点/歼击机/航母/发射导弹/爆炸效果/激光雷达等)

一、简单介绍 雷达模拟仿真工具&#xff0c;主要通过模拟点模拟相关物体&#xff0c;方位、航向角、距离、速度&#xff0c;并且显示相关详情信息可建立跟踪线建立与模拟点联系。可自定义更换模拟点背景达到更加逼真效果&#xff0c;如歼击机&#xff0c;航母发射导弹效果&…

单例模式,适配器模式,迭代器模式,工厂模式(C++实现)

设计模式就相当于编程中的“孙子兵法”&#xff0c;是经过很久的时间以及各路大神总结出来的多种实用&#xff0c;高效的业务设计中的套路; 单例模式 一个类只能创建一个对象的情况下的一种设计模式&#xff08;eg&#xff1a;服务器只有一个&#xff09;&#xff0c;即单例模…

【论文阅读】《知识图谱研究综述》;Knowledge Graph:概念及主要应用,主要特征、构建的主要技术、未来研究方向。

目录 1. 文章来源2. 简介3. 什么是知识图谱4. Knowledge Graph 主要特征5. Knowledge Graph 构建的主要技术6. Knowledge Graph 未来研究方向1. 文章来源 该paper来自于知网,请尊重原创,这里仅作为学习笔记~ 2. 简介 知识图谱将知识库以一种图谱的形式展现出来,使知识具有…

opencv-python常用函数解析及参数介绍(一)——图像读取及其通道与灰度

opencv-python中一些常用函数解析及参数介绍前言1.读图片读彩图读灰度图2.显示图片显示彩图显示灰度图灰度图与彩图的区别从彩图中分离单通道通道合并3.保存图片4.灰图的通道分离5.读取视频前言 本文将简单介绍opencv-python中的图像以及视频的读取&#xff0c;并且介绍灰度图…

Hello 2023

年初到年尾&#xff0c;感觉刚写完《Hello 2022》&#xff0c;就要迎接2023了。今天是2022最后一天&#xff0c;本来是元旦假期的第一天&#xff0c;奈何要来公司加班&#xff0c;抽个时间来回顾下 2022 吧。 2022 年和2021 一样也是里程碑的一年&#xff0c;完成了两件大事&a…

使用CSS提高网站性能的30种方法

根据httparchive.org的页面重量报告&#xff0c;CSS在平均70个请求和2MB的网页上占7个HTTP请求和70Kb的代码。这并不是网站性能糟糕的最坏原因&#xff08;我正看着你呢&#xff0c;JavaScript&#xff09;&#xff0c;但CSS面临着特定的挑战&#xff1a; CSS会阻止渲染&#x…

【C++进阶】多态

文章目录多态的概念多态的定义及实现抽象类多态的原理多继承和单继承关系中的虚表函数继承和多态常见的问题多态的概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。 …

我的2022年,一位双非生的平淡一年

没有白走的路&#xff0c;每一步都算数&#x1f388;&#x1f388;&#x1f388; 前言 一谈到年终总结&#xff0c;其实还是很多的遗憾&#xff0c;我和大部分人一样&#xff0c;走过这一年的路途&#xff0c;发现自己除去每年头发越来越少&#xff0c;脸色日渐不佳。好像没有什…

Web 和移动应用程序测试之间的区别

智能手机改变了人类与技术互动的方式。无论是旅行、健身、生活方式、视频游戏&#xff0c;甚至是服务&#xff0c;都只需触手可及(字面意思就是如此)。我们只需要看看越来越多的智能手机或平板电脑用户与桌面用户就可以掌握这一现实。 根据一项调查&#xff0c;从 2021 年 4 月…

Dubbo(尚硅谷)学习笔记3

这是我们正常启动&#xff1a; 现在我们去把zookeeper注册中心关掉。 此时我们的注册中心是用不了的。 但是我们的数据还有&#xff0c;也就是我们的消费者还是能调用我们的提供者中的方法。 那么我们现在来试一下dubbo直连&#xff0c;也就是没有注册中心&#xff0c;我们也可…

VideoRender和ImageRender中的一些c++知识点

1.inline C中的inline用法_路痴的旅行的博客-CSDN博客 1 引入inline关键字的原因在c/c中&#xff0c;为了解决一些频繁调用的小函数大量消耗栈空间&#xff08;栈内存&#xff09;的问题&#xff0c;特别的引入了inline修饰符&#xff0c;表示为内联函数&#xff0c;栈空间就是…

深入理解计算机系统_静态链接和动态链接以及静态库和动态库

这篇文章记录静态链接和动态链接以及静态库和动态库的原理。 1 静态链接和动态链接 链接其实就是连接的意思&#xff0c;将所有相关的东西连接起来。 1.1 静态链接 什么是静态链接&#xff1f;编译时候的链接就是静态链接&#xff0c;所以ld/collect2链接程序&#xff0c;也…

公司让我一个人干数据中台,是不是可以准备找下家了

大数据群里&#xff0c;有个哥们问了下面这样一个问题&#xff0c;让刚刚阳康返工的群友们笑的心跳加速&#xff0c;直接炸锅。 开工有惊喜 一个人搞一个数据中台&#xff01;这是啥神仙领导做出来的决策&#xff1f;是发烧的时候拍脑袋定的吗&#xff1f;热心的群友也都给出了…