1.概述
当前端解析完SQL命令后,需要对数据库进行操作时,会通过B-Tree模块查找需要的页面,B-Tree维护着磁盘各页面之间的复杂关系,B-Tree不会直接读写磁盘,它会通过调用pager模块来获取所需的页面或修改页面,pager模块的作用可以说是B-Tree和磁盘读写的中间代理。
pager模块作为事务管理器,实现了数据库的ACID特性,从而支持并发控制和存储失败后的恢复;作为一个数据管理器,定义了固定长度的页面来访问数据库文件,通过页面缓存来加快页面的查找。事务管理器和数据管理器的实现又依赖3个子模块,分别为锁管理、缓存管理和日志管理,如下图
其中锁管理和日志管理在SQLite的原子提交特性中起着关键作用,所谓原子提交意味着在一个事务中数据库的变化会完整完成或者根本不完成,不会出现写数据到一半后断电或系统崩溃造成数据库损坏的现象。关于原子提交,官方有详细的说明文档介绍,以下是别人的翻译
http://blog.youkuaiyun.com/javensun/article/details/8515690
可以先看这篇文档了解原子提交的基本概念,后续会再结合源代码详细说明原子提交的实现原理。
要详细了解pager层的相关概念和原理,可以参阅《SQLite Database System Design and Implementation》这本书。
2.Pager初始化
初步了解pager模块的结构后,我们就可以阅读源码了。当建立一个数据库连接时,需要调用sqlite3PagerOpe()创建一个pager对象,这个对象是一个复杂的结构体,包含了访问数据库操作的各种资源,该结构体如下
struct Pager {
sqlite3_vfs *pVfs; /* 访问磁盘IO所用的文件系统接口 */
u8 exclusiveMode; /* 是否在使用EXCLUSIVE锁 */
u8 journalMode; /* 日志模式 */
u8 useJournal; /* 是否使用日志 */
u8 noSync; /* 如果为真,不对日志写入磁盘,如数据库是内存模式的存储 */
u8 fullSync; /* 修改日志后是否立即刷盘 */
u8 extraSync; /* 删除日志后立即刷新磁盘 */
//以下3个参数最终都会传递给OsSync()决定是否立即刷盘
u8 ckptSyncFlags; /* SYNC_NORMAL or SYNC_FULL for checkpoint */
u8 walSyncFlags; /* SYNC_NORMAL or SYNC_FULL for wal writes */
u8 syncFlags; /* SYNC_NORMAL or SYNC_FULL otherwise */
u8 tempFile; /* 临时文件或只读文件 */
u8 noLock; /* Do not lock (except in WAL mode) */
u8 readOnly; /* True for a read-only database */
u8 memDb; /* 内存模式的数据库,不对磁盘操作 */
/**************************************************************************
在模块内的成员决定pager的state,其他成员决定pager的configuration
** The following block contains those class members that change during
** routine operation. Class members not in this block are either fixed
** when the pager is first created or else only change when there is a
** significant mode change (such as changing the page_size, locking_mode,
** or the journal_mode). From another view, these class members describe
** the "state" of the pager, while other class members describe the
** "configuration" of the pager.
*/
u8 eState; /* Pager state (OPEN, READER, WRITER_LOCKED..) */
u8 eLock; /* Current lock held on database file */
u8 changeCountDone; /* Set after incrementing the change-counter */
u8 setMaster; /* True if a m-j name has been written to jrnl */
u8 doNotSpill; /* Do not spill the cache when non-zero */
u8 subjInMemory; /* True to use in-memory sub-journals */
u8 bUseFetch; /* True to use xFetch() */
u8 hasHeldSharedLock; /* True if a shared lock has ever been held */
Pgno dbSize; /* Number of pages in the database */
Pgno dbOrigSize; /* dbSize before the current transaction */
Pgno dbFileSize; /* Number of pages in the database file */
Pgno dbHintSize; /* Value passed to FCNTL_SIZE_HINT call */
int errCode; /* One of several kinds of errors */
int nRec; /* Pages journalled since last j-header written */
u32 cksumInit; /* Quasi-random value added to every checksum */
u32 nSubRec; /* Number of records written to sub-journal */
Bitvec *pInJournal; /* One bit for each page in the database file */
sqlite3_file *fd; /* File descriptor for database */
sqlite3_file *jfd; /* File descriptor for main journal */
sqlite3_file *sjfd; /* File descriptor for sub-journal */
i64 journalOff; /* Current write offset in the journal file */
i64 journalHdr; /* Byte offset to previous journal header */
sqlite3_backup *pBackup; /* Pointer to list of ongoing backup processes */
PagerSavepoint *aSavepoint; /* Array of active savepoints */
int nSavepoint; /* Number of elements in aSavepoint[] */
u32 iDataVersion; /* Changes whenever database content changes */
char dbFileVers[16]; /* Changes whenever database file changes */
int nMmapOut; /* Number of mmap pages currently outstanding */
sqlite3_int64 szMmap; /* Desired maximum mmap size */
PgHdr *pMmapFreelist; /* List of free mmap page headers (pDirty) */
/*
** End of the routinely-changing class members
***************************************************************************/
u16 nExtra; /* Add this many bytes to each in-memory page */
i16 nReserve; /* Number of unused bytes at end of each page */
u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */
u32 sectorSize; /* Assumed sector size during rollback */
int pageSize; /* Number of bytes in a page */
Pgno mxPgno; /* Maximum allowed size of the database */
i64 journalSizeLimit; /* Size limit for persistent journal files */
char *zFilename; /* Name of the database file */
char *zJournal; /* Name of the journal file */
int (*xBusyHandler)(void*); /* Function to call when busy */
void *pBusyHandlerArg; /* Context argument for xBusyHandler */
int aStat[3]; /* Total cache hits, misses and writes */
#ifdef SQLITE_TEST
int nRead; /* Database pages read */
#endif
void (*xReiniter)(DbPage*); /* Call this routine when reloading pages */
int (*xGet)(Pager*,Pgno,DbPage**,int); /* Routine to fetch a patch */
#ifdef SQLITE_HAS_CODEC
void *(*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */
void (*xCodecSizeChng)(void*,int,int); /* Notify of page size changes */
void (*xCodecFree)(void*); /* Destructor for the codec */
void *pCodec; /* First argument to xCodec... methods */
#endif
char *pTmpSpace; /* Pager.pageSize bytes of space for tmp use */
PCache *pPCache; /* Pointer to page cache object */
#ifndef SQLITE_OMIT_WAL
Wal *pWal; /* Write-ahead log used by "journal_mode=wal" */
char *zWal; /* File name for write-ahead log */
#endif
};
现在先对pager的成员变量有一个初步的认识,可以看到大概这些成员都与锁、日志、读写文件、读写page、cahce等操作相关,在结构体声明的上方源码给出一些重要成员的详细说明,这里先不说了,后续随着代码的深入对每个变量会有更细致的分析。
接着我们来分析sqlite3PagerOpen()都做了些什么,该函数的原型为
int sqlite3PagerOpen(
sqlite3_vfs *pVfs, /* The virtual file system to use */
Pager **ppPager, /* OUT: Return the Pager structure here */
const char *zFilename, /* Name of the database file to open */
int nExtra, /* Extra bytes append to each in-memory page */
int flags, /* 该标志位是否使用日志或是否是内存数据库 */
int vfsFlags, /* flags passed through to sqlite3_vfs.xOpen() */
void (*xReinit)(DbPage*) /* Function to reinitialize pages */
)
如果zFilename不为空,说明数据库存在磁盘上,调用sqlite3OsFullPathname()获取文件的路径全名zPathname,再取得路径长度nPathname。
pPtr是一个u8型的指针,由路径长度,就可以一次性给pager模块分配所需的全部内存:
pPtr = (u8 *)sqlite3MallocZero(
ROUND8(sizeof(*pPager)) + /* Pager structure */
ROUND8(pcacheSize) + /* PCache object */
ROUND8(pVfs->szOsFile) + /* The main db file */
journalFileSize * 2 + /* The two journal files */
nPathname + 1 + nUri + /* zFilename */
nPathname + 8 + 2 /* zJournal */
#ifndef SQLITE_OMIT_WAL
+ nPathname + 4 + 2 /* zWal */
#endif
再把得到的内存分配到pager模块的各子对象里:
pPager = (Pager*)(pPtr);
pPager->pPCache = (PCache*)(pPtr += ROUND8(sizeof(*pPager)));
pPager->fd = (sqlite3_file*)(pPtr += ROUND8(pcacheSize));
pPager->sjfd = (sqlite3_file*)(pPtr += ROUND8(pVfs->szOsFile));
pPager->jfd = (sqlite3_file*)(pPtr += journalFileSize);
pPager->zFilename = (char*)(pPtr += journalFileSize);
assert( EIGHT_BYTE_ALIGNMENT(pPager->jfd) );
/* Fill in the Pager.zFilename and Pager.zJournal buffers, if required. */
if( zPathname ){
assert( nPathname>0 );
pPager->zJournal = (char*)(pPtr += nPathname + 1 + nUri);
memcpy(pPager->zFilename, zPathname, nPathname);
if( nUri ) memcpy(&pPager->zFilename[nPathname+1], zUri, nUri);
memcpy(pPager->zJournal, zPathname, nPathname);
memcpy(&pPager->zJournal[nPathname], "-journal\000", 8+2);
sqlite3FileSuffix3(pPager->zFilename, pPager->zJournal);
#ifndef SQLITE_OMIT_WAL
pPager->zWal = &pPager->zJournal[nPathname+8+1];
memcpy(pPager->zWal, zPathname, nPathname);
memcpy(&pPager->zWal[nPathname], "-wal\000", 4+1);
sqlite3FileSuffix3(pPager->zFilename, pPager->zWal);
#endif
sqlite3DbFree(0, zPathname);
如果是普通数据库,根据路径名打开数据库文件:
rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout);
文件描述符存放在pPager->fd指针对象里,pPager->fd是一个sqlite3_file*类型的指针,不同的vfs有不同的扩展,这个在之前介绍vfs已经说过。之后会设置页面的大小,默认是4096,还会设置pPager->sectorSize,最小是512。
接着主要是调用sqlite3PagerSetPagesize()设置页面大小并为Pager.pTmpSpace分配空间,调用sqlite3PcacheOpen()创建cache并初始化,最后就是pager对象结构体里的一些变量的初始化。