SQLite3源碼學習(8)Pager模塊概述及初始化
1.概述
當前端解析完SQL命令后,需要對數據庫進行操作時,會通過B-Tree模塊查找需要的頁面,B-Tree維護著磁盤各頁面之間的復雜關系,B-Tree不會直接讀寫磁盤,它會通過調用pager模塊來獲取所需的頁面或修改頁面,pager模塊的作用可以說是B-Tree和磁盤讀寫的中間代理。
pager模塊作為事務管理器,實現了數據庫的ACID特性,從而支持并發控制和存儲失敗后的恢復;作為一個數據管理器,定義了固定長度的頁面來訪問數據庫文件,通過頁面緩存來加快頁面的查找。事務管理器和數據管理器的實現又依賴3個子模塊,分別為鎖管理、緩存管理和日志管理,如下圖
其中鎖管理和日志管理在SQLite的原子提交特性中起著關鍵作用,所謂原子提交意味著在一個事務中數據庫的變化會完整完成或者根本不完成,不會出現寫數據到一半后斷電或系統崩潰造成數據庫損壞的現象。關于原子提交,官方有詳細的說明文檔介紹,以下是別人的翻譯
http://blog.csdn.net/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對象結構體里的一些變量的初始化。
智能推薦
nginx0.1.0之http模塊初始化源碼分析(3)
我們繼續分析ngx_http_block函數的代碼,指令解析完,繼續執行各子模塊的鉤子函數。 1 init_main_conf 只有charset模塊實現了init_main_conf函數。 2 merge_srv_conf 我們繼續看一下各個模塊的merge_srv_conf函數。 http_core模塊 rewrite模塊 ssl模塊 3 merge_loc_conf access模塊 cha...
dubbo源碼學習(一)——初始化
解析服務 dubbo對服務的解析是基于dubbo.jar中的META-INF/spring.handlers配置。 以下為dubbo配置: 以下為META-INF/spring.handlers配置 對應源碼文件路徑為: 先看spring解析命名空間代碼: spring處理Document元素時,將Document文檔內元素具體解析工作委托給BeanDefinitionParserD...
springmvc學習一初始化源碼
這兩天,學習了一下springmvc的源碼,主要是學習了啟動和調用的流程,主要分以下兩部分來記錄筆記 1.啟動流程 2.調用流程 springmvc源碼,先概括的說一下 1、初始化handlerMapping對象、初始化handlerAdapter對象;在初始化handlerMapping對象的時候,會解析所有的bean,將controller和對應的URL存入到對應的map集合中 2、在調用的時...
Sqlite3入門學習(一)
Linux環境下sqlite3的安裝及常用Linux API說明 環境安裝 Step1:一般的linux可能會自帶sqlite,在安裝之前先使用sqlite3命令檢測一下,若顯示并未安裝,則進行下述操作 Step2:先到 https://www.sqlite.org/download.html ,下載sqlite-autoconf-*.tar.gz壓縮包 Step3:下載完了...
猜你喜歡
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...