Sqlite Source Code

Table of Contents

1. SQlite: Source Code

sqlite3.c 分为以下几个模块:

  1. parser
  2. vdbe
  3. btree
  4. pager
  5. pcache

1.1. data structure

1.1.1. sqlite3

struct sqlite3 {
    sqlite3_vfs *pVfs;            /* OS Interface */
    struct Vdbe *pVdbe;           /* List of active virtual machines */
    CollSeq *pDfltColl;           /* The default collating sequence (BINARY) */
    sqlite3_mutex *mutex;         /* Connection mutex */
    Db *aDb;                      /* All backends */
    int nDb;                      /* Number of backends currently in use */
    int flags;                    /* Miscellaneous flags. See below */
    i64 lastRowid;                /* ROWID of most recent insert (see above) */
    unsigned int openFlags;       /* Flags passed to sqlite3_vfs.xOpen() */
    int errCode;                  /* Most recent error code (SQLITE_*) */
    int errMask;                  /* & result codes with this before returning */
    u8 autoCommit;                /* The auto-commit flag. */
    u8 temp_store;                /* 1: file 2: memory 0: default */
    u8 mallocFailed;              /* True if we have seen a malloc failure */
    u8 dfltLockMode;              /* Default locking-mode for attached dbs */
    signed char nextAutovac;      /* Autovac setting after VACUUM if >=0 */
    u8 suppressErr;               /* Do not issue error messages if true */
    u8 vtabOnConflict;            /* Value to return for s3_vtab_on_conflict() */
    u8 isTransactionSavepoint;    /* True if the outermost savepoint is a TS */
    int nextPagesize;             /* Pagesize after VACUUM if >0 */
    u32 magic;                    /* Magic number for detect library misuse */
    int nChange;                  /* Value returned by sqlite3_changes() */
    int nTotalChange;             /* Value returned by sqlite3_total_changes() */
    int aLimit[SQLITE_N_LIMIT];   /* Limits */
    struct sqlite3InitInfo {      /* Information used during initialization */
        int newTnum;                /* Rootpage of table being initialized */
        u8 iDb;                     /* Which db file is being initialized */
        u8 busy;                    /* TRUE if currently initializing */
        u8 orphanTrigger;           /* Last statement is orphaned TEMP trigger */
    } init;
    int activeVdbeCnt;            /* Number of VDBEs currently executing */
    int writeVdbeCnt;             /* Number of active VDBEs that are writing */
    int vdbeExecCnt;              /* Number of nested calls to VdbeExec() */
    int nExtension;               /* Number of loaded extensions */
    void **aExtension;            /* Array of shared library handles */
    void (*xTrace)(void*,const char*);        /* Trace function */
    void *pTraceArg;                          /* Argument to the trace function */
    void (*xProfile)(void*,const char*,u64);  /* Profiling function */
    void *pProfileArg;                        /* Argument to profile function */
    void *pCommitArg;                 /* Argument to xCommitCallback() */   
    int (*xCommitCallback)(void*);    /* Invoked at every commit. */
    void *pRollbackArg;               /* Argument to xRollbackCallback() */   
    void (*xRollbackCallback)(void*); /* Invoked at every commit. */
    void *pUpdateArg;
    void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
#ifndef SQLITE_OMIT_WAL
    int (*xWalCallback)(void *, sqlite3 *, const char *, int);
    void *pWalArg;
#endif
    void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*);
    void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*);
    void *pCollNeededArg;
    sqlite3_value *pErr;          /* Most recent error message */
    char *zErrMsg;                /* Most recent error message (UTF-8 encoded) */
    char *zErrMsg16;              /* Most recent error message (UTF-16 encoded) */
    union {
        volatile int isInterrupted; /* True if sqlite3_interrupt has been called */
        double notUsed1;            /* Spacer */
    } u1;
    Lookaside lookaside;          /* Lookaside malloc configuration */
#ifndef SQLITE_OMIT_AUTHORIZATION
    int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
    /* Access authorization function */
    void *pAuthArg;               /* 1st argument to the access auth function */
#endif
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
    int (*xProgress)(void *);     /* The progress callback */
    void *pProgressArg;           /* Argument to the progress callback */
    int nProgressOps;             /* Number of opcodes for progress callback */
#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
    int nVTrans;                  /* Allocated size of aVTrans */
    Hash aModule;                 /* populated by sqlite3_create_module() */
    VtabCtx *pVtabCtx;            /* Context for active vtab connect/create */
    VTable **aVTrans;             /* Virtual tables with open transactions */
    VTable *pDisconnect;    /* Disconnect these in next sqlite3_prepare() */
#endif
    FuncDefHash aFunc;            /* Hash table of connection functions */
    Hash aCollSeq;                /* All collating sequences */
    BusyHandler busyHandler;      /* Busy callback */
    Db aDbStatic[2];              /* Static space for the 2 default backends */
    Savepoint *pSavepoint;        /* List of active savepoints */
    int busyTimeout;              /* Busy handler timeout, in msec */
    int nSavepoint;               /* Number of non-transaction savepoints */
    int nStatement;               /* Number of nested statement-transactions  */
    i64 nDeferredCons;            /* Net deferred constraints this transaction. */
    int *pnBytesFreed;            /* If not NULL, increment this in DbFree() */

#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
    /* The following variables are all protected by the STATIC_MASTER 
    ** mutex, not by sqlite3.mutex. They are used by code in notify.c. 
    **
    ** When X.pUnlockConnection==Y, that means that X is waiting for Y to
    ** unlock so that it can proceed.
    **
    ** When X.pBlockingConnection==Y, that means that something that X tried
    ** tried to do recently failed with an SQLITE_LOCKED error due to locks
    ** held by Y.
    */
    sqlite3 *pBlockingConnection; /* Connection that caused SQLITE_LOCKED */
    sqlite3 *pUnlockConnection;           /* Connection to watch for unlock */
    void *pUnlockArg;                     /* Argument to xUnlockNotify */
    void (*xUnlockNotify)(void **, int);  /* Unlock notify callback */
    sqlite3 *pNextBlocked;        /* Next in list of all blocked connections */
#endif
}

1.1.2. sqlite3_vfs

e.g. sqlite3->pVfs

struct sqlite3_vfs {
    // vfs 中 xOpen 的参数 sqlite3_file 的"子类"代表了不同平台上的
    // file, 例如 unixFile, winFile, ..., 每个"子类"的成员和大小都不同,
    // 例如 unixFile 会包含 unixInodeInfo, unixShm 这些和 unix 相关的
    // 成员, 当使用 xOpen 时, 调用者需要根据 szOsFile 初始化相应大小的
    // 内存以容纳 unixFile 或者 winFile
    int szOsFile;            /* Size of subclassed sqlite3_file */

    // sqlite3OsInit 时会通过 sqlite3_vfs_register 注册多个
    // sqlite3_vfs, 例如 "unix_nolock" -> unixVfs, 这里的 vfs 的区别并
    // 不是指不同的平台例如 unix, win (平台的不同会通过编译宏来区分),而
    // 不是指同一个平台上不同的实现.

    // 这些 vfs 通过 pNext 组织起来. vfs_register 只会在第一次调用
    // sqlite3OsInit 时被调用一次 sqlite3OsInit 后, 以后每次调用
    // sqlite3_open_v2 时, 会根据传入的 zVfs 参数通过 sqlite3_vfs_find
    // 找到对应的 sqlite3_vfs, 并赋给 sqlite3->pVfs
    sqlite3_vfs *pNext;      /* Next registered VFS */

    const char *zName;       /* Name of this virtual file system */
    void *pAppData;          /* Pointer to application-specific data
                              * */

    // xOpen 用来打开文件 (包括 db, journal, wal 文件), 无论打开文件是
    // 否成功, sqlite3_file 的 sqlite3_io_methods 都需要被赋值. 以
    // sqlite3PagerOpen 为例, sqlite3PagerOpen 会把 sqlite3_file 赋给
    // pPager->fd
    int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
                 int flags, int *pOutFlags);

    int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
    int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
    int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut);
    void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
    void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
    void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void);
    void (*xDlClose)(sqlite3_vfs*, void*);
    int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
    int (*xSleep)(sqlite3_vfs*, int microseconds);
    int (*xCurrentTime)(sqlite3_vfs*, double*);
    int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*);
    int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr);
    sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName);
    const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName);
}

1.1.3. sqlite3_file

e.g. Pager->fd

sqlite3_file 是 xOpen 要返回的结果, 代表一个打开了的文件, 其"子类"包括

  1. unixFile, winFile, os2File …
  2. JournalFile, MemJournal

sqlite3_file 这个"父类"只规定了一个字段 sqlite3_io_methods, 不同的子类需要实现相应的 io method, 例如 xRead, xWrite, xClose …, 除此之外, 子类还定义了和自身相关的字段, 例如 unixFile 会定义 inode 信息.

Pager 就是通过 pager->fd 这个 sqlite3_file 来读取 db 文件的.

1.1.4. sqlite3_io_methods

e.g. sqlite3_file->pMethods

1.1.5. struct sqlite3_mem_methods

e.g. sqlite3GlobalConfig->m

通过 sqlite3_config 可以提供一个自定义的 sqlite3_mem_methods, 负责内存的分配与释放. 与一般的 malloc 不同的是, sqlite3_mem_methods 提供了一个 xSize 方法用来获取一个指针所对应的已分配内存的大小. 具体的实现是 xMalloc 时把内存大小信息写到了内存的前一个 int64 的位置.

struct sqlite3_mem_methods {
    void *(*xMalloc)(int);         /* Memory allocation function */
    void (*xFree)(void*);          /* Free a prior allocation */
    void *(*xRealloc)(void*,int);  /* Resize an allocation */
    int (*xSize)(void*);           /* Return the size of an allocation */
    int (*xRoundup)(int);          /* Round up request size to allocation size */
    int (*xInit)(void*);           /* Initialize the memory allocator */
    void (*xShutdown)(void*);      /* Deinitialize the memory allocator */
    void *pAppData;                /* Argument to xInit() and xShutdown() */
};

1.1.6. PgHdr

struct PgHdr {
    sqlite3_pcache_page *pPage;    /* Pcache object page handle */
    void *pData;                   /* Page data */
    void *pExtra;                  /* Extra content */
    PgHdr *pDirty;                 /* Transient list of dirty pages */
    Pager *pPager;                 /* The pager this page is part of */
    Pgno pgno;                     /* Page number for this page */
    u32 pageHash;                  /* Hash of page content */  
    /* flags 可能为 dirty, need_sync, need_read, dont_write 等 */
    u16 flags;                     /* PGHDR flags defined below */

    i16 nRef;                      /* Number of users of this page */
    PCache *pCache;                /* Cache that owns this page */
    PgHdr *pDirtyNext;             /* Next element in list of dirty pages */
    PgHdr *pDirtyPrev;             /* Previous element in list of dirty pages */
}

1.1.7. Schema

struct Schema {
    int schema_cookie;   /* Database schema version number for this file */
    int iGeneration;     /* Generation counter.  Incremented with each change */
    Hash tblHash;        /* All tables indexed by name */
    Hash idxHash;        /* All (named) indices indexed by name */
    Hash trigHash;       /* All triggers indexed by name */
    Hash fkeyHash;       /* All foreign keys by referenced table name */
    Table *pSeqTab;      /* The sqlite_sequence table used by AUTOINCREMENT */
    u8 file_format;      /* Schema format version for this file */
    u8 enc;              /* Text encoding used by this database */
    u16 flags;           /* Flags associated with this schema */
    int cache_size;      /* Number of pages to use in the cache */
};

1.1.8. lookaside

struct Lookaside {
    u16 sz;                 /* Size of each buffer in bytes */
    u8 bEnabled;            /* False to disable new lookaside allocations */
    u8 bMalloced;           /* True if pStart obtained from sqlite3_malloc() */
    int nOut;               /* Number of buffers currently checked out */
    int mxOut;              /* Highwater mark for nOut */
    int anStat[3];          /* 0: hits.  1: size misses.  2: full misses */
    LookasideSlot *pFree;   /* List of available buffers */
    void *pStart;           /* First byte of available memory space */
    void *pEnd;             /* First byte past end of available space */
};

1.1.9. unixInodeInfo

struct unixInodeInfo {
    struct unixFileId fileId;       /* The lookup key */
    int nShared;                    /* Number of SHARED locks held */
    unsigned char eFileLock;        /* One of SHARED_LOCK, RESERVED_LOCK etc. */
    unsigned char bProcessLock;     /* An exclusive process lock is held */
    int nRef;                       /* Number of pointers to this structure */
    unixShmNode *pShmNode;          /* Shared memory associated with this inode */
    int nLock;                      /* Number of outstanding file locks */
    UnixUnusedFd *pUnused;          /* Unused file descriptors to close */
    unixInodeInfo *pNext;           /* List of all unixInodeInfo objects */
    unixInodeInfo *pPrev;           /*    .... doubly linked */
}

由于 posix 的文件锁实现对于多线程是有问题的 (参考源码中 Posix Advisory Locking 部分), 所以 sqlite 使用了一个 unixInodeInfo 记录一个进程中对同一个 inode 的加锁情况.

同一个进程的多个线程可能会打开同一个 db 文件多次, 对应多少 unixFile, 但因为这些 unixFile 对应同一个 inode, 所以 unixFile->pInode 指向同一个 unixInodeInfo 结构.

这样当同一个进程的某个线程对某 unixFile 获得了 shared lock 时, 另一个线程再想对同一个 inode 的另一个 unixFile 加 shared lock 时, 不再需要调用 unixFileLock 真正加锁, 而是通过 nShared + 1 获得 shared lock.

1.1.10. MemPage

Btree 眼中的 page, 包括一个对 DbPage 的引用, 以及 page 内部的信息, 例如 cell, overflow 等

1.1.11. Btree

1.1.12. BtShared

1.1.13. BtCursor

1.1.14. Mem

1.1.15. 总结

  • Db ->Btree -> BtShared -> Pager -> pcache, 以上各个结构体都对应了一个数据库文件.
  • BtShared 包含多个 BtCursor, 代表正在操作的各个表.

1.2. Pcache

通过 sqlite3_pcache_methods2 结构体, 用户可以通过 sqlite3_config 指定一个自定义的 pcache 实现. sqlite3 使用 pcache1 做为默认的 pcache 实现.

1.2.1. sqlite3PcacheFetch

pager 负责调用 sqlite3PcacheFetch (通过 sqlite3PagerAcquire)

int sqlite3PcacheFetch(
    PCache *pCache,       /* Obtain the page from this cache */
    Pgno pgno,            /* Page number to obtain */
    int createFlag,       /* If true, create page if it does not exist already */
    PgHdr **ppPage        /* Write the page here */
  ):
  // 通过 pcach1 获得一个 page
  pPage = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate);
  // 若 pcach1 无法分配一个 page: 例如 pcache1 满了而 lru list 为空
  if( !pPage && eCreate==1 ):
    // 从 pcache 自己维护的 pDirty 中找到一个 dirty 的但 nRef 为 0 的 page
    // pDirty 中的 page 一定不存在于 pcach1 的 lru list 中.
    for(pPg=pCache->pDirtyTail; pPg && pPg->nRef; pPg=pPg->pDirtyPrev);
    // 调用 pagerStress, 把 pPg 这个空闲的 dirty page 刷新到数据库中
    // 刷新后会调用 pPg
    rc = pCache->xStress(pCache->pStress, pPg);
      sqlite3PcacheMakeClean(pPg);
        pcacheRemoveFromDirtyList(p);
        // unpin 会导致这个 page 被放入 lru list
        pcacheUnpin(p);
    // 再次调用 pcache1Fetch
    pPage = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, 2);
  // page 的 nRef + 1
  pPgHdr->nRef++;

1.2.2. pcache1Fetch

// p 是 pcache 实例
// iKey 实际上是 pgno
// pcache 模块本身涉及到 dirty, 
static sqlite3_pcache_page *pcache1Fetch(       
       sqlite3_pcache *p, 
       unsigned int iKey, 
       int createFlag):
  // 通过 pcache1 的 apHash 查找, apHash 中可能包含了所有的 cached
  // page, 包含 pinned (不存在于 lru list), unpinned (存在于 lru list)
  // 以及 dirty 的等
  /* Step 1: Search the hash table for an existing entry. */
  if( pCache->nHash>0 ):
    unsigned int h = iKey % pCache->nHash;
    for(pPage=pCache->apHash[h]; pPage&&pPage->iKey!=iKey; pPage=pPage->pNext);
  // 若 pPage 找到, 则返回这个 pPage, 但返回之前先通过 pcache1PinPage
  // 确保这个 page 不存在于 lru list 中, 因为这个 lru list 的意义是一
  // 个对象缓存, 存放着那些已经没有意义的对象
  if( pPage || createFlag==0 ):
    pcache1PinPage(pPage);
    goto fetch_out;

  /* Step 3: Abort if createFlag is 1 but the cache is nearly full */
  // 若 createFlag==1, 但 pcache1 已经快满了, 则返回空
  if( createFlag==1 && (
    nPinned>=pGroup->mxPinned
    || nPinned>=pCache->n90pct
    || pcache1UnderMemoryPressure(pCache)
    ):
    goto fetch_out;

  /* Step 4. Try to recycle a page. */
  if( pCache->bPurgeable && pGroup->pLruTail && (
    (pCache->nPage+1>=pCache->nMax)
    || pGroup->nCurrentPage>=pGroup->nMaxPage
    || pcache1UnderMemoryPressure(pCache)
    )):
    // 尝试从 lru list 中获得一个无用的 page
    // 从 lru list 的队尾获取一个 page
    pPage = pGroup->pLruTail;
    // 从 apHash 中去掉这个 page, 因为过一会儿这个 page 的内容都重写后
    // 其 key 与 value 不再对应. 
    pcache1RemoveFromHash(pPage);
    // 把 page 从 lru list 中移除
    pcache1PinPage(pPage);

  /* Step 5. If a usable page buffer has still not been found, 
  ** attempt to allocate a new one. 
  */
  if( !pPage ):
    // 分配一个新的 page, 并加入于 apHash 中
    pPage = pcache1AllocPage(pCache);
    pCache->apHash[h] = pPage;

1.2.3. 总结

  1. pcache 的 dirty list

    dirty list 不仅在 fetch 时可以用来刷新 page 到 db 以便获得更多的 free page, 更重要的是在 commit 阶段可以根据这个 list 知道哪些 pcache 需要被刷新到 db 文件中

  2. pcache1 的 lru list

    lru list 是一个对象缓存, 通过 pcache1Unpin 可以把一个无用的 page 放到 lru list 中. 凡是存在于 lru list 的 page 其 nRef 都为 0, 表示无人使用.

    dirty list 和 lru list 没有交集.

  3. pcache1 的 apHash

    apHash 中的 page 可能存在于 dirty list 中, 也可能存在于 lru list 中

1.3. RowSet

RowSet 是 sqlite3 的一个用来支持 RowSet 相关 OP code 的数据结构. RowSet 相关的 OP code 包括:

  1. RowSetAdd
  2. RowSetRead
  3. RowSetTest

主要用来实现 `delete where`, `update where` 等功能: 因为 btree 无法在遍历的同时执行 delete, update 等操作, 所以需要用 RowSet 来暂存遍历的结果.

而且 RowSet 本身有排序的功能, RowSetRead OP code 每次都返回最小的一个值.

1.4. WAL

1.4.1. sqlite3WalBeginReadTransaction

sqlite3PagerSharedLock -> pagerBeginReadTransaction -> sqlite3WalBeginReadTransaction

sqlite3WalBeginReadTransaction 相当对当前的 wal journal 打开一个 read snapshot: 获得一把 WAL_READ_LOCK, 设定一个相应的 read mark.

这些 READ_LOCK 和 read mark 可以确保 walCheckpoint 时只将合适的部分 wal journal 内容写回数据库文件.

1.4.2. walCheckpoint

walCheckpoint 并不是把 wal journal 所有的内容都写回到 db 中: 因为此时有可能有 wal reader 正在使用 wal. 例如, 若当前有三个 reader, 其 read mark 分别为 4,6,8, 则 walCheckpoint 可能只会把 4 之前的内容写回到 db, 因为若此时将 5 写回, 后续 reader 1 可能从 db 中读到修改过的 5, 而这是违反了数据库的隔离性的.

1.4.3. sqlite3WalFrames

若使用 WAL journal mode, 则在 commit 之前, 对数据库的修改操作只会导致 pcache 的变化, wal journal 不会受任何影响 (若使用 delete journal, 则在 commit 之前, 任何修改数据库的操作就会通过 sqlite3PagerWrite 导致 delete journal 被修改)

在 commit 时, 被修改的 pcache 会被写入到 wal journal (通过 sqlite3WalFrames)

sqlite3PagerCommitPhaseOne
  pagerWalFrames(pPager, pList, pPager->dbSize, 1);
    sqlite3WalFrames(  Wal *pWal,                      /* Wal handle to write to */
      int szPage,                     /* Database page-size in bytes */
      PgHdr *pList,                   /* List of dirty pages to write */
      Pgno nTruncate,                 /* Database size after this commit */
      int isCommit,                   /* True if this is a commit */
      int sync_flags                  /* Flags to pass to OsSync() (or 0) */pPager)
      // pList 是所有 dirty 的 pcache 页
      iFrame = pWal->hdr.mxFrame;
      // mxFrame 是 wal 中当前最大的 frame 号, 若 mxFrame 为 0, 说明 wal 日志为空
      if( iFrame==0 ):
        // 写 wal 头到日志
        sqlite3OsWrite(pWal->pWalFd, aWalHdr, sizeof(aWalHdr), 0);
      /* Write all frames into the log file exactly once */
      for(p=pList; p; p=p->pDirty){
        walWriteOneFrame(&w, p, nDbSize, iOffset);

      // 对每一个新写入的 frame, 更新 wal index
      for(p=pList; p && rc==SQLITE_OK; p=p->pDirty){
        walIndexAppend(pWal, iFrame, p->pgno);

wal 日志中, 每次 commit 都会导致日志中对应 dirty page 的新 iframe 被添加, 所以, 若同一个 page 在多次 commit 中都被修改了, 则 wal 中会包含对应这个 page 的多个 iframe, 相当于该 page 在不同 commit 时间的 snapshot. 所以 wal 日志相对于 delete journal 会占用更多的空间.

1.4.4. sqlite3WalClose

wal 文件在数据库连接关闭时会被删除:

sqlite3PagerClose
  sqlite3WalClose
    // 需要先获得对 db 文件的 exclusive lock
    // 而在 wal 模式下, shared lock 在 commit 时并不会释放, 所以
    // 这里实际上会导致最后一个连接关闭时才删除 wal 文件
    sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE);
    sqlite3WalCheckpoint
    walIndexClose(pWal, isDelete);
    sqlite3OsClose(pWal->pWalFd);
    sqlite3OsDelete(pWal->pVfs, pWal->zWalName, 0);

另外, 每次 commit 之后, 若 wal journal 过大 (当前 mxFrame 指示的大小当于真正的文件大小, 说明文件后面一部分实际是无用的), 则会通过 walLimitSize 尝试把文件大小 truncate 为 journal_size_limit 这个 pragma 指定的大小.

注意的是 journal_size_limit 并不能限制日志文件的最大大小, 只是说在某些情况下需要把文件 truncate 到这个大小.

若 wal 在使用时, 进程异常终止, 而磁盘会遗留一个 wal 文件, 而下一次使用 wal 日志时, sqlite3 会调用 walIndexRecover 来根据 wal 日志重建 wal index.

1.4.5. walLimitSize

在 sqlite3WalFrames 时, 若当前设置了 journal_size_limit, 则会根据这个值将 wal journal truncate. 与 wal 不同的是, rollback journal 的 truncate 是发生在 sqlite3BtreeCommitPhaseTwo -> pager_end_transaction -> zeroJournalHdr 时.

1.4.6. WAL Index

wal index 本意是一个共享内存, 多个 sqlite 进程都可以通过这个 index 获得当前 wal 日志的信息, 实际实现上, 这个共享内存只是对 xxx-shm 文件的 mmap.

wal index 是为了解决这样一个问题:

某个 connection 在打开 wal 时, 当时最大的 iFrame 是 X, 则对于 wal 中所有 <= X 的 iFrame, 与 pgno 对应的最大的 iFrame 是哪个? 即 iFrame = Hash(pgno, X)

wal 本身维持着多个锁, 例如 WAL_READ_LOCK, WAL_WRITE_LOCK, WAL_CKPT_LOCK 等, 这些锁彼此之间一般没有关联, 例如 walCheckpoint 时要求获得对 WAL_CKPT_LOCK 的 exclusive lock, 防止多个 checkpoint 同时进行, 这时可以允许有其他连接持有 WAL_READ_LOCK 对 wal 进行读取.

1.5. Rollback Journal

1.5.1. hasHotJournal

检测一个 journal 是否是 hot journal 很简单: 若 journal header 不为空, 则为 hot journal.

要手工生成 hot journal 是很困难的. 因为 hot journal 只在以下情况下才能产生:

  1. 在 synchronous 为 FULL/NORMAL 的情况下, CommitPhaseOne 通过 syncJournal 将 journal sync 成功 sync 到磁盘, 但在 CommitPhaseTwo 时因为掉电等原因没有将日志 delete (或 truncate, persist)
  2. 在 synchronous 为 NORMAL 的情况下, 在 syncJournal 因为掉电没有完全成功 (只有 journal header 被写到磁盘)

1.5.2. syncJournal

在一个 transaction commit 之前, sqlite3PagerWrite 会不断的将原始的 page 加入到 journal 中. 但此时 journal header 是空白的, 以防将来误认为是一个 hot journal.

syncJournal
  // 构造 zHeader, 包含 magic 和 nRec
  u8 zHeader[sizeof(aJournalMagic)+4];
  memcpy(zHeader, aJournalMagic, sizeof(aJournalMagic));
  put32bits(&zHeader[sizeof(aJournalMagic)], pPager->nRec);
  if( pPager->fullSync):
    // 若为 fullSync 模式, 将在写 header 到 journal 之前, 先将 journal
    // sync
    sqlite3OsSync(pPager->jfd, pPager->syncFlags);
  // 将 header 写到日志
  sqlite3OsWrite(pPager->jfd, zHeader, sizeof(zHeader), pPager->journalHdr);
  // 再 sync 一次, 此后日志变为 hot journal
  sqlite3OsSync(pPager->jfd, ...)
  pPager->eState = PAGER_WRITER_DBMOD;

1.5.3. pager_playback

pager_playback 发生在两个时机:

  1. rollback
  2. 打开数据库时 sqlite3PagerSharedLock 通过 hasHotJournal 检测到 hot journal 后, 调用 pager_playback
int pager_playback(Pager *pPager, int isHot):
  // 获得 journal 文件大小, 后面可能会使用这个值来确定 journal 中有几
  // 个 page
  sqlite3OsFileSize(pPager->jfd, &szJ);
  // 从 journal header 中读到 nRec, mxPg.
  // 对于 hot journal, nRec 在 journal header 是有记录的, 但对于
  // 通过 rollback 发起的 playback, 此时 journal 并没有 sync 到磁盘,
  // 其 nRec 也是没有值的 (0)
  readJournalHdr(pPager, isHot, szJ, &nRec, &mxPg);

  // 若 nRec 没有值, 说明不是 hot journal, 通过 szJ 计算 nRec
  /* If nRec is 0 and this rollback is of a transaction created by this
  ** process and if this is the final header in the journal, then it means
  ** that this part of the journal was being filled but has not yet been
  ** synced to disk.  Compute the number of pages based on the remaining
  ** size of the file.

  if( nRec==0 && !isHot &&
    pPager->journalHdr+JOURNAL_HDR_SZ(pPager)==pPager->journalOff ):
    nRec = (int)((szJ - pPager->journalOff) / JOURNAL_PG_SZ(pPager));

  for(u=0; u<nRec; u++):
    // 对 journal 中每一个 page 进行 playback.
    // 但实际上, 并不是每个 page 都会被 playback,
    // 例如, persist 模式的 journal 中实际会包含一些旧的, 并非
    // 本次 transaction 修改过的 page, 这些是不能被 playback 的
    // pager_playback_one_page 会通过 journal header 的 checksum 与
    // journal 中各个 page 的 checksum 来决定这个 page 是否需要 playback
    // 每次通过 pager_write -> pager_open_journal 时, journal header
    // 的 checksum 会被初始化一个随机数, 确定每次 transaction 时这个值
    // 都是不同的
    pager_playback_one_page(pPager,&pPager->journalOff,0,1,0);
      read32bits(jfd, (*pOffset)-4, &cksum);
      if pager_cksum(pPager, (u8*)aData)!=cksum:
        // 这个 SQLITE_DONE 导致上层函数退出循环, playback 结束
        return SQLITE_DONE;
      // 找到 对应的 pcache, 若不存在, 说明该 pgno 在 pcache 中没有对
      // 应. 
      pPg = pager_lookup(pPager, pgno);
      // 若 eState 显示该 pager 已经 sync 过, 则需要写数据库文件
      if (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN):
        sqlite3OsWrite(pPager->fd, (u8*)aData, pPager->pageSize, ofst);
      if( pPg ):
        // pcache 中存在这个 page, 则修改 pcache 的值
        pData = pPg->pData;
        memcpy(pData, (u8*)aData, pPager->pageSize);
        sqlite3PcacheMakeClean(pPg);

1.6. Pager

1.6.1. pager_playback

1.6.2. readDbPage

readDbPage 负责从数据库中读取某个 page 对应的内容用来填充从 pcache 获取的新的 page. 调用路径是

sqlite3PagerAcquire -> sqlite3PcacheFetch -> pcache1Fetch -> readDbPage

实现上 readDbPage 并不一定从 db 文件中获取数据: 若使用 wal, 则可能从 wal 中获取数据.

int readDbPage(PgHdr *pPg):
  if pagerUseWal(pPager):
    /* Try to pull the page from the write-ahead log. */
    sqlite3WalRead(pPager->pWal, pgno, &isInWal, pgsz, pPg->pData);
  else:
    sqlite3OsRead(pPager->fd, pPg->pData, pgsz, iOffset);

1.6.3. syncJournal

在 sqlite3PagerCommitPhaseOne 时, 若当前没有使用 wal, 则会调用 syncJournal 将 journal sync 到磁盘.

if( NOT <in-memory journal> ){
  // 若 synchronous == FULL, 则在 update journal header 之前 (主要是
  // nRec 和 magic number), 先执行一次 sync 将 journal 内容先写进磁盘
  // 因为这时 magic 部分为空, 所以这时的 journal 还不是 hot journal
  if( <full-sync mode> ) xSync(<journal file>);
  // 根据 pager->nRec 更新 journal 的 nRec, 并添加 magic
  <update nRec field>
  // 再 sync 一次 journal 文件, 这时包括文件头
  // 可见, synchronous == FULL 时会比 synchronous == NORMAL 时多一次
  // sync 操作
  xSync(<journal file>);
}

1.6.4. pager_write_pagelist

在 sqlite3PagerCommitPhaseOne 时, syncJournal 完成后, 通过 pager_write_pagelist 把 dirty pages 写到数据库

1.6.5. sqlite3PagerSync

把 db sync 到磁盘. 在 sqlite3PagerCommitPhaseOne 时 pager_write_pagelist 后被调用.

1.6.6. pagerStress

pagerStress 是一个回调函数, 在 sqlite3PagerOpen 时, 通过 sqlite3PcacheOpen 把 pagerStress 注册到 pCache->xStress.

pCache->xStress 在 sqlite3PagerAcquire-> sqlite3PcacheFetch 会被调用: 首先调用 pcache1Fetch(createFlag==0) 尝试获取从 pcache1 中获得一个 page, 若 pcache1 本身的 LRU list 已经没有空闲了, 并且 pCache dirty list 中包含一个 dirty 的, 但 nRef 为 0 的 page, 则这个 page 是可以通过写回到数据库而释放的. 所以 pcache 会调用 xStress 来写回这个 page, 然后再次调用 pcache1Fetch (上一步的 xStress 必然会导致有一个 page 被加入到 pcache1 的 LRU list)

pagerStress 要做的实际就是在 pcache 满了以后将某个 dirty page 写到 db, 因为要改写 db, 所以 journal 也需要被 sync. 整个过程和 commit 一类似的.

1.6.7. sqlite3PagerAcquire

sqlite3PagerAcquire 是对 sqlite3PcacheFetch 以及 pcache1Fetch 的封装, 主要负责调用 pcache 获得一个 page, 若这个 page 是新的, 则用 readDbPage 把这个 page 的内容读进来. 并更新 nPage->nHit, nPage->nMiss 这些统计信息.

1.6.8. sqlite3PagerShrink

1.6.9. pager_open_journal

1.6.10. sqlite3PagerSharedLock

sqlite3PagerSharedLock 是所有 transaction 开始的第一步: OP_Transaction 会通过 lockBtree 调用 sqlite3PagerSharedLock.

获得 shared lock, 对于 rollback journal mode, 会检查 hot journal 以及进行 pager_playback. 对于 wal journal mode, 会获得一个 WAL_READ_LOCK, 通过 walTryBeginRead 获得一个 snapshot, 并检测是否需要 walIndexRecover

if !pagerUseWal(pPager):
  pager_wait_on_lock(pPager, SHARED_LOCK);
  hasHotJournal(pPager, &bHotJournal);
  if bHotJournal:
    pagerLockDb(pPager, EXCLUSIVE_LOCK);
    pagerSyncHotJournal(pPager);
    pager_playback(pPager, 1);
if pagerUseWal(pPager):
  pagerBeginReadTransaction(pPager);
    sqlite3WalBeginReadTransaction
      walTryBeginRead
        walIndexReadHdr
          walIndexRecover
        walLockShared

1.6.11. sqlite3PagerBegin

sqlite3PagerBegin 是获取 RESERVED_LOCK 的入口函数. 其调用路径是:

OP_Transaction (WRITE_MODE) -> sqlite3BtreeBeginTrans -> sqlite3PagerBegin

sqlite3PagerBegin 主要作用就是获得 RESERVED_LOCK, 表示要开始一个写操作.

insert/delete/update 这个语句都会被 parser 编译为 OP_Transaction(WRITE_MODE) 语句, 这就是 insert/delete/update 语句执行时会获得 RESERVED_LOCK 的原因.

若当前没使用 wal, 则对 db 获得 RESERVED_LOCK, 若使用 wal, 则通过 sqlite3WalBeginWriteTransaction 获到 wal 的 WRITE_LOCK. 所以在 wal 模式下, 也不能同时有两个 writer 的.

1.6.12. sqlite3PagerWrite && pager_write

当 btree 决定要修改某个 page (pcache) 之前, 例如 sqlite3BtreeInsert, 必须先调用 sqlite3PagerWrite 将这个 page 的原始内容写到日志中. 当然这只有对 rollback journal 才会有这个步骤, 因为 wal journal 并不需要这样做.

pager_write 一次写一个 page 到 rollback journal 中. 但 sqlite3PagerWrite 会根据 sector 的大小考虑更多: 例如, 若 sector 为 4k 而 page 为 1K, 因为磁盘控制器每次总是以 sector 为单位写, 所以 sqlite 必须把连续 4 个 page 写入到一个 sector 中, 否则若只写一个 page, 其他三个 page 的内容可能被写成 0.

不过因为 sector 大小通常都为 512, 所以实际上 sqlite3PagerWrite 和 pager_wirte 基本是一样的, 即每次只会写一个 page.

另外, 虽然每次 btreeXXX 要对 page 进行修改时都需要调用 pager_write, 但 pager_write 并不需要每次都写 page 到 rollback journal: 若 rollback journal 中已经包含这个 page 的原始内容 (pageInJournal), 则这个 pager_write 是不需要执行的.

1.6.13. sqlite3PagerCommitPhaseOne

sqlite3PagerCommitPhaseOne 是 commit 的第一个阶段, 主要任务是 sync 日志和数据库文件

sqlite3PagerCommitPhaseOne
  /* If no database changes have been made, return early. */
  if (pPager->eState<PAGER_WRITER_CACHEMOD) return SQLITE_OK;
  if( MEMDB ) return SQLITE_OK;
  if( pagerUseWal(pPager) ):
    // 若是 wal, 则只通过 pagerWalFrames 修改 wal 日志
    pagerWalFrames(pPager, pList, pPager->dbSize, 1);
  else:
    pager_incr_changecounter(pPager, 1);
    syncJournal(pPager, 0);
    pager_write_pagelist(pPager,sqlite3PcacheDirtyList(pPager->pPCache));
    // 此时所有 pcache 中的 page 与 db 都是一致的了, 把 dirty list 置
    // 空, 并且 nRef 为 0 的 page 进入 lru list
    sqlite3PcacheCleanAll(pPager->pPCache);
    sqlite3PagerSync(pPager);

1.6.14. sqlite3PagerCommitPhaseTwo

sqlite3PagerCommitPhaseTwo 主要用来清理 rollback journal. 或者对于 wal 来说, drop write_lock

1.6.15. sqlite3PagerRollback

对 pager_playback 的封装

1.6.16. sqlite3PagerOpen

1.6.17. sqlite3PagerClose

1.7. Btree

1.7.1. Btree

1.7.2. BtShared

1.7.3. BtCursor

1.8. VDBE

1.8.1. Mem

1.9. 内存分配

1.9.1. alloc

sqlite 会使用以下函数来分配内存:

1.9.1.1. sqlite3Malloc

对 malloc 的封装, 并且加上了 size 的支持 (可以通过 sqlite3MallocSize 获得 malloc 分配的大小).

另外, sqlite3Malloc 也是后面提到的 scratch, lookaside, pcache 三种分配方式的 fallback.

实际上 sqlite3Malloc 可以使用多个版本的 memory system:

  1. SQLITE_SYSTEM_MALLOC

    默认的 malloc 实现

  2. SQLITE_MEMDEBUG

    这个版本的 sqlite3MemMalloc 会包含一些 memory debug 信息, 例如 backtrace, FOREGUARD, REARGUARD 等.

  3. SQLITE_ENABLE_MEMSYS3

    一个基于 memory pool 的分配器

  4. SQLITE_ENABLE_MEMSYS5

    一个与 buddy 系统类似的分配器

1.9.1.2. sqlite3PageMalloc

对 pcache 的封装, 用来给 page 分配内存.

1.9.1.3. sqlite3DbMalloc

当给其他对象分配内存时, 例如给 parser, vdbe 等, 会使用 sqlite3DbMalloc, 它会优先使用 lookside 这种分式来分配内存

lookaside 类似于一个简化版的 slab 分配器 (slot 大小是固定的), 默认配置为 128 bytes * 500, 主要用来分配一些小的内存.

void *sqlite3DbMallocRaw(sqlite3 *db, int n)
  // 若 n 比 lookaside 的 slot 大, 则无法用 lookaside 分配
  // 通过 anStat[1]++ 加一些 log 信息
  if( n>db->lookaside.sz ):
    db->lookaside.anStat[1]++;
  else if( (pBuf = db->lookaside.pFree)==0 ):
    // 大小合适但 slot 用完了, lookaside 分配失败
    db->lookaside.anStat[2]++;
  else:
    // 取出一个 slot
    db->lookaside.pFree = pBuf->pNext;
    // checkout slot 数 +1
    db->lookaside.nOut++;
    db->lookaside.anStat[0]++;
    return (void*)pBuf;
  // lookaside 分配失败, 使用 malloc 分配  
  p = sqlite3Malloc(n);

lookaside malloc 在 openDatabase 被初始化:

openDatabase()
  // 默认配置下, szLookaside 为 128 bytes, nLookaside 为 500
  setupLookaside(db, 0, sqlite3GlobalConfig.szLookaside, sqlite3GlobalConfig.nLookaside);
1.9.1.4. sqlite3ScratchMalloc

scratch malloc 的作用是 alloca 类似, 对某些对象可以分配在栈上的, 会使用这个函数.

1.9.2. status

1.9.2.1. sqlite3_memory_highwater
1.9.2.2. sqlite3_memory_used
1.9.2.3. sqlite3HeapNearlyFull

1.9.3. setting

1.9.3.1. sqlite3_soft_heap_limit64

若设置了 soft_heap_limit, 则后续通过 sqlite3Malloc 时, 若通过 mem0 发现已分配内存将接近 soft limit, 则通过 sqlite3MallocAlarm 方法触发 softHeapLimitEnforcer 这个 alarm, 后者会调用 sqlite3_release_memory,进行调用 sqlite3PcacheReleaseMemory 释放 pcache 的内存.

1.9.4. lookaside

1.9.5. scratch

1.9.6. pcache

1.9.7. Other

通过 adb shell dumpsys meminfo pid 可以 dump 出来 sqlite 内存分配相关的信息

1.10. 文件锁

1.10.1. shared_lock

所有 vdbe 执行时, 都会最先在 OP_Transaction 时通过 sqlite3PagerSharedLock 获取 shared_lock. 此时还会包含检测 hot-journal 的动作

1.10.2. reserved_lock

OP_Transaction 使用了 wrflag = 1 时, 表示该 vdbe 会写数据库, 这时会通过 sqlite3BtreeBeginTrans -> sqlite3PagerBegin 获得 reserved_lock

1.10.3. exclusive_lock

OP_Halt 时, vdbeCommit 会获得 exclusive_lock

1.11. query

下面使用的测试语句及 byte code 如下:

sqlite> explain select * from test;
0|Trace|0|0|0||00|
1|Goto|0|9|0||00|
2|OpenRead|0|3|0|1|00|test
3|Rewind|0|7|0||00|
4|Column|0|0|1||00|test.name
5|ResultRow|1|1|0||00|
6|Next|0|4|0||01|
7|Close|0|0|0||00|
8|Halt|0|0|0||00|
9|Transaction|0|0|0||00|
10|VerifyCookie|0|1|0||00|
11|TableLock|0|3|0|test|00|
12|Goto|0|2|0||00|

1.11.1. sqlite3_open_v2

1.11.1.1. sqlite3_initialize
sqlite3_initialize
  // 初始化 mutex 系统
  sqlite3MutexInit
    if(!sqlite3GlobalConfig.mutex.xMutexAlloc):
      // 用户可以通过 sqlite3_config(SQLITE_CONFIG_MUTEX, xxx) 指定一个自
      // 定义的 mutex 实现 ... 若没有指定, 则根据平台的不同选择一个默认
      // 的 mutex 实现
      if(sqlite3GlobalConfig.bCoreMutex):
        // 若 sqlite 的编译时选项指定了 sqlite 为 SINGLETHREAD,
        // 或者通过 sqlite3_config 指定了 SQLITE_CONFIG_SINGLETHREAD 则 bCoreMutex
        // 会为空, 这时整个 mutex 系统会不起作用: 不论是 core mutex 还是
        // 和 db->mutex 相关的 full mutex
        pFrom = sqlite3DefaultMutex();
        // sqlite3DefaultMutex 在编译时会根据平台选择合适的版本
      else:
        pFrom = sqlite3NoopMutex();
        // sqlite3NoopMutex 中所有函数都为空操作.
      memcpy(&sqlite3GlobalConfig.mutex, pFrom, offsetof(sqlite3_mutex_methods, xMutexAlloc));

  // 初始化 malloc 系统 
  sqlite3MallocInit();
    // 用户可以通过 sqlite_config(SQLITE_CONFIG_MALLOC,xxx)
    // 指定一个自定义的 malloc 实现
    if(sqlite3GlobalConfig.m.xMalloc==0):
        // 平台相关的默认实现
        sqlite3MemSetDefault();

  // 初始化各种自定义函数, 如 sum, like ..., 把这些函数通过
  // sqlite3FuncDefInsert 加入到一个全局的 hash map 中, 这些函数包括:
  // trim, min, max, typeof, length, substr, round, upper, lower,
  // hex, random, nullif, sqlite_version, sqlite_log, last_insert_rowid,
  // sum, total, avg, like, 等
  sqlite3RegisterGlobalFunctions();

  // 初始化 pcache
  sqlite3PcacheInitialize();
  // 用户可以通过 sqlite_config 指定一个自定义 pcache 实现
  if(sqlite3GlobalConfig.pcache2.xInit==0):
    // 设置 pcache 相关的函数为默认的函数, 如 pcache1Init,
    // pcache1Truncate, xxx
    sqlite3PCacheSetDefault();

  // 初始化平台相关的函数, 主要是和文件系统相关, 例如 xOpen, xDelete
  // 等, 也有少量和 vfs 无关的函数, 例如 xSleep, xCurrentTime 等
  // 注意, io 相关的方法如 xRead, xWrite 等不属于 vfs 相关, 也不
  // 由 sqlite3OsInit 有初始化: xOpen 会负责把相应的 sqlite3_io_methods
  // 设置到 sqlite3_file->pMethods 上
  sqlite3OsInit();

总结:

sqlite3_initialize 会初始化 malloc, mutex, pcache, 这三个子系统都是可以通过 sqlite3_config 设置为一个用户自定义实现的. 另外还要初始化平台相关的 vfs 实现. 因为初始化时需要修改一些全局的变量, 所以需要 mutex 子系统必须先初始化成功. 若当前模式为 SINGLETHREAD, 则会因为 bCoreMutex 为假导致 mutex 系统没有初始化, 进行导致上层应用无法正常在两个线程中同时打开数据库.

1.11.1.2. 初始化 db
openDatabase
  sqlite3_initialize
  // 根据 mutex 的配置决定是否启用 db 相关的 db->mutex
  // 最终的结果是: 若 sqlite3_config 指定了 SERIALIZED
  // 或者 sqlite3_open_v2 时指定了 FULLMUTEX 选项, 则
  // db->mutex 会被启用.
  // 根据 sqlite3_config, sqlite3_open_v2 或编译选项不同
  // bCoreMutex 和 bFullMutex 会被不同的置位.
  // sqlite 内部有两种 mutex, 一种是通过
  // sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER) 这种形式
  // 建立的静态的锁, 一共有五个, 对应于 sqlite 中几个不同的
  // critical area 的加锁.  另一种是通过 db->mutex 保存的和
  // 单个 db 有关的锁.
  // 若 bCoreMutex 无效, 则所有 mutex 都无效, 不能有多个线程同时执行
  // sqlite 相关的代码, 对应于 SINGLETHREAD 的情 况.
  // 若 bCoreMutex 有效, bFullMutex 无效, 则那几个静态的锁有效,
  // 表示可以有多个线程同时操作不同的 db connection, 对应于
  // MULTITHREAD .若 bCoreMutex 有效, bFullMutex 有效, 则
  // db->mutex 也是有锁保护的, 同一个 db connection 可以在不同的
  // thread 中同时使用, 对应于 SERIALIZED.

  if(sqlite3GlobalConfig.bCoreMutex==0):
    isThreadsafe = 0;
  else if(flags & SQLITE_OPEN_NOMUTEX):
    isThreadsafe = 0;
  else if(flags & SQLITE_OPEN_FULLMUTEX):
    isThreadsafe = 1;
  else:
    isThreadsafe = sqlite3GlobalConfig.bFullMutex;
  if(isThreadsafe):
    db->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE);

  // 设置默认的 collation
  createCollation(db, "BINARY", SQLITE_UTF8, 0, binCollFunc, 0);
  createCollation(db, "RTRIM", SQLITE_UTF8, (void*)1, binCollFunc, 0);
  createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0);

  // 默认 collation 为 BINARY
  db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "BINARY", 0);

  // 初始化 btree 模块, 打开数据库文件
  sqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, 0, flags |SQLITE_OPEN_MAIN_DB);
    // 真正打开文件
    sqlite3PagerOpen(pVfs, &pBt->pPager, zFilename, ..)
    // 读取文件头, 获得 page size 等, 注意 SHORT_READ 不算错误.
    sqlite3PagerReadFileheader(pBt->pPager,sizeof(zDbHeader),zDbHeader);
    // 设置默认的 busy handler
    sqlite3PagerSetBusyhandler(pBt->pPager, btreeInvokeBusyHandler, pBt);
    // 若上一步成功读到了文件头, 则根据文件头设置 page size
    sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, nReserve);

  // sqlite 支持通过 attach 的方法在一个 db connection 中打开多个 db.
  // 这些 db 都放在 aDb 数据中. 默认初始时有两个 db, 一个 main, 代表
  // sqlite 启动时找开的 db, 还有一个 temp 表示临时 db, 例如通过
  // create temp table 建立的表都放在 temp db 中.
  // 例如:
  // select * from main.test == select * from test;
  // create temp table test2 (name TEXT);
  // select * from temp.test2;
  db->aDb[0].zName = "main";
  db->aDb[0].safety_level = 3;
  db->aDb[1].zName = "temp";
  db->aDb[1].safety_level = 1;

  // 自动加载一个 extension, 参考 http://www.sqlite.org/loadext.html
  sqlite3AutoLoadExtensions(db);

  // 初始化一些扩展模块: fts, icu, r-tree
  sqlite3Fts1Init(db);
  sqlite3IcuInit(db);
  sqlite3RtreeInit(db);
1.11.1.3. 总结

sqlite3_open_v2 的过程:

  1. 会初始化 malloc, mutex, pcache 等相关的回调函数, 根据平台的不同注册一些 vfs 相关的回调函数
  2. 初始化 mutex, 注册一些 min,max, sum 等内部函数, 注册 collation 函数.
  3. 然后打开 btree 和 pager 模块, 使用 pager 从文件头中读取 page size 设置到 pager.
  4. 加载其他 extension, 初始化 fts, icu, r-tree 模块.

在 pager 初始化时, 会真正打开数据库文件并读取文件头, 其他的内容例如 schema 在 sqlite3_open_v2 过程中暂时不会读取: 后面第一次访问数据库时会读取 schema (即 sqlite_master 表)

1.11.2. sqlite3_prepare

sqlite3LockAndPrepare
  // prepare 时需要 lock 住 db->mutex, 即 bFullMutex 模式
  // 因为只有 SERIALIZED 模式下 db->mutex 才起作用, 
  // 所以非 SERIALIZED 模式下不能在不同线程同时调用 sqlite3_prepare,
  // 否则会出错
  sqlite3_mutex_enter(db->mutex);
  sqlite3Prepare(db, zSql, nBytes, saveSqlFlag, pOld, ppStmt, pzTail);
    sqlite3RunParser(pParse, zSqlCopy, &zErrMsg);
      // the main parser program
      sqlite3Parser();
        do{
          yy_reduce(yypParser,yyact-YYNSTATE);
            switch( yyruleno ):
              case 112: /* cmd ::= select */
                sqlite3Select(pParse, yymsp[0].minor.yy159, &dest);
                  sqlite3SelectPrep(pParse, p, 0);
                    sqlite3SelectExpand(pParse, p);
                      sqlite3WalkSelect(&w, pSelect);
                        sqlite3LocateTable()
                          // 在 parse 阶段需要 read schema
                          sqlite3ReadSchema();
              case xxx:
                // xxx

    *ppStmt = (sqlite3_stmt*)pParse->pVdbe;    

1.11.3. sqlite3_step

sqlite3_step(stmt)
  sqlite3_mutex_enter(db->mutex);
  sqlite3Step(stmt)
    sqlite3VdbeExec(vdbe);
    // sqlite3VdbeExec 相当于一个解释器, 内部实现就是一个 while (true)
    // {swich} 循环
      for(pc=p->pc; rc==SQLITE_OK; pc++){
        OP_GOTO:

        OP_Transaction:
          sqlite3BtreeBeginTrans(u.at.pBt, pOp->p2);
            lockBtree
              sqlite3PagerSharedLock
                pager_wait_on_lock(pPager, SHARED_LOCK);
                // 检测是否有 hot journal
                if hasHotJournal():
                  pagerLockDb(pPager, EXCLUSIVE_LOCK);
                  pagerSyncHotJournal(pPager);
              // if write mode
              sqlite3PagerBegin(pBt->pPager,wrflag>1,sqlite3TempInMemory(p->db));
                // 获得 reserved_lock
                pagerLockDb(pPager, RESERVED_LOCK);

        OP_OpenRead:
          allocateCursor(p, pOp->p1, u.aw.nField, u.aw.iDb, 1);
          sqlite3BtreeCursor(u.aw.pX, ...);
        OP_Rewind:
          sqlite3BtreeFirst(u.bl.pCrsr, &u.bl.res);
          moveToRoot(pCur);
            getAndInitPage;
              btreeGetPage;
                sqlite3PagerAcquire;
                  sqlite3PcacheFetch
                  readDbPage;
                    sqlite3OsRead
        OP_Column:
          // 从 cursor 中取出数据放在 vdbe->aMem 中
        OP_ResultRow:
           // 这一句 byte code 正常会返回 SQLITE_ROW, 而不是 SQLITE_OK,
           // 导致最外层 for 返回, 整个 sqlite3_step 结束
           // 下次 step 将以 p->pc 为起点, 即 OP_Next
           p->pc = pc + 1;
           rc = SQLITE_ROW;
           goto vdbe_return;
        OP_Next:
          sqlite3BtreeNext(u.bm.pCrsr, &u.bm.res);
            moveToChild;
              getAndInitPage;
        OP_Halt:
          sqlite3VdbeHalt(p);
          if autoCommit == 1:
            vdbeCommit(db, p);
              // 获得 exclusive lock
              sqlite3PagerExclusiveLock(sqlite3BtreePager(pBt));
              sqlite3BtreeCommitPhaseOne
              sqlite3BtreeCommitPhaseTwo
                btreeEndTransaction
                  releasePage
                    // 释放锁
                    pagerUnlockAndRollback;

1.11.3.1. 总结
  1. OP_Transaction 时 sqlite3BtreeBeginTrans 会获得 shared lock
  2. OP_Halt 时 btreeEndTransaction 需要释放锁
  3. sqlite3PagerAcquire 会调用 pcache 和 pager 来获得 page 的数据. 发生的时机是 btree 移动时(例如 OP_Rewind, OP_Next)
  4. OP_Transaction 会检测 hot journal 并 sync journal

1.11.4. journal 操作

1.11.4.1. pager_write

pager_wirte 会导致 journal file 被写入

1.11.4.2. syncJournal

在 sqlite3PagerCommitPhaseOne 时, syncJournal 被调用确保 journal 被 sync 到磁盘

1.11.4.3. hasHotJournal

在 OP_Transaction (sqlite3PagerSharedLock) 时, 检测 hot journal 是否存在

1.11.4.4. pagerSyncHotJournal

在 hot journal 存在, 则调用该方法 sync hot journal

1.12. insert

sqlite> explain insert into test values ("a");
0|Trace|0|0|0||00|
1|Goto|0|9|0||00|
2|OpenWrite|0|3|0|1|00|test
3|NewRowid|0|2|0||00|
4|String8|0|3|0|a|00|
5|MakeRecord|3|1|4|a|00|
6|Insert|0|4|2|test|1b|
7|Close|0|0|0||00|
8|Halt|0|0|0||00|
9|Transaction|0|1|0||00|
10|VerifyCookie|0|1|0||00|
11|TableLock|0|3|1|test|00|
12|Goto|0|2|0||00|

1.12.1. sqlite3_step

OP_NewRowid:
  // NewRowid 需要为该新记录生成一个唯一的 rowid,
  // 根据 schema 的不同分为两种情况:
  // 1. 假设记录中使用了 integer primary key autoincrement 类型的字段 _id, 这
  // 时 rowid 内部实现上直接使用这个 _id 字段, _id 不仅需要唯一: 还需
  // 要自增. sqlite 通过 sqlite_sequence 这张表来控制这种情况下 _id 的生成
  // 2. schema 中并没指定 autoincrement 这种类型的字段. 这时 NewRowid
  // 需要生成一个唯一的 rowid, sqlite 定义了两种算法:
  // 1) 扫描全表, 找到已经使用的最大 rowid +1, 得到新的 rowid
  // 2) 随机生成一个 rowid, 通过查表确定是否冲突, 若冲突再重新生成一个
  // 不论哪种算法, 扫描全表都是必须的. 

OP_Insert:
  sqlite3BtreeInsert(u.bh.pC->pCursor, 0, u.bh.iKey,...)
    // 找到对应的 page
    btreeMoveto(pCur, pKey, nKey, appendBias, &loc);
    // 写到 cell 中
    fillInCell(pPage, newCell, pKey, nKey, pData, nData, nZero,&szNew);
    // sqlite3PagerWrite 会导致 journal 被写入
    sqlite3PagerWrite(pPage->pDbPage);

Author: [email protected]
Date: 2017-09-09 Sat 00:00
Last updated: 2020-09-10 Thu 12:32

知识共享许可协议