- 浏览: 29536 次
- 性别:
- 来自: 广州
最新评论
写在前面:个人认为pager层是SQLite实现最为核心的模块,它具有四大功能:I/O,页面缓存,并发控制
和日志恢复。而这些功能不仅是上层Btree的基础,而且对系统的性能和健壮性有关至关重要的影响。其
中并发控制和日志恢复是事务处理实现的基础。SQLite并发控制的机制非常简单——封锁机制;别外,它
的查询优化机制也非常简单——基于索引。这一切使得整个SQLite的实现变得简单,SQLite变得很小,运
行速度也非常快,所以,特别适合嵌入式设备。好了,接下来讨论事务的剩余部分。 7、
日志文件刷入磁盘(Flushing The Rollback Journal File To Mass Storage)
代码如下:
8、
获取排斥锁(Obtaining An Exclusive Lock)
9、
修改的页面写入文件(Writing Changes To The Database File)
以上两步的实现代码:
10、
修改结果刷入存储设备(Flushing Changes To Mass Storage)
最后来看看这几步是如何实现的:
其实以上以上几步是在函数
sqlite3BtreeSync
()---btree.c中
调用的(而关于该函数的调用后面再讲)。
代码如下:
下图可以进一步解释该过程:
6、修改位于用户进程
空间的页面(Changing Database Pages In User Space)
页面的原始数据写入日
志之后,就可以修改页面了——位于用户进程空间。每个数据库连接都有自己私有的空间,所以页面的变化只对该连接可见,而对其它连接的数据仍然是磁盘缓存中
的数据。从这里可以明白一件事:一个进程在修改页面数据的同时,其它进程可以继续进行读操作。
图
中的红色表示修改的页面。
接
下来把日志文件的内容刷入磁盘,这对于数据库从意外中恢复来说是至关重要的一步。而且这通常也是一个耗时的操作,因为磁盘I/O速度很慢。
这个步
骤不只把日志文件刷入磁盘那么简单,它的实现实际上分成两步:首先把日志文件的内容刷入磁盘(即页面数据);然后把日志文件中页面的数目写入日志文件头,
再把header刷入磁盘(这一过程在代码中清晰可见
)。
/*
**Sync 日志文件,保证所有的脏页面写入磁盘日志文件
*/
static int syncJournal(Pager *pPager){
PgHdr *pPg;
int rc = SQLITE_OK;
/* Sync the journal before modifying the main database
** (assuming there is a journal and it needs to be synced.)
*/
if( pPager->needSync ){
if( !pPager->tempFile ){
assert( pPager->journalOpen );
/* assert( !pPager->noSync ); // noSync might be set if synchronous
** was turned off after the transaction was started. Ticket #615 */
#ifndef NDEBUG
{
/* Make sure the pPager->nRec counter we are keeping agrees
** with the nRec computed from the size of the journal file.
*/
i64 jSz;
rc = sqlite3OsFileSize(pPager->jfd, &jSz);
if( rc!=0 ) return rc;
assert( pPager->journalOff==jSz );
}
#endif
{
/* Write the nRec value into the journal file header. If in
** full-synchronous mode, sync the journal first. This ensures that
** all data has really hit the disk before nRec is updated to mark
** it as a candidate for rollback.
*/
if( pPager->fullSync ){
TRACE2("SYNC journal of %d\n", PAGERID(pPager));
//首先保证脏页面中所有的数据都已经写入日志文件
rc = sqlite3OsSync(pPager->jfd, 0);
if( rc!=0 ) return rc;
}
rc = sqlite3OsSeek(pPager->jfd,
pPager->journalHdr + sizeof(aJournalMagic));
if( rc ) return rc;
//页面的数目写入日志文件
rc = write32bits(pPager->jfd, pPager->nRec);
if( rc ) return rc;
rc = sqlite3OsSeek(pPager->jfd, pPager->journalOff);
if( rc ) return rc;
}
TRACE2("SYNC journal of %d\n", PAGERID(pPager));
rc = sqlite3OsSync(pPager->jfd, pPager->full_fsync);
if( rc!=0 ) return rc;
pPager->journalStarted = 1;
}
pPager->needSync = 0;
/* Erase the needSync flag from every page.
*/
//清除needSync标志位
for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
pPg->needSync = 0;
}
pPager->pFirstSynced = pPager->pFirst;
}
#ifndef NDEBUG
/* If the Pager.needSync flag is clear then the PgHdr.needSync
** flag must also be clear for all pages. Verify that this
** invariant is true.
*/
else{
for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
assert( pPg->needSync==0 );
}
assert( pPager->pFirstSynced==pPager->pFirst );
}
#endif
return rc;
}
在对数据库文件进行修改之前(注:这里不是
内存中的页面),我们必须得到数据库文件的排斥锁(Exclusive Lock)。得到排斥锁的过程可分为两步:首先得到Pending
lock;然后Pending lock升级到exclusive lock。
Pending lock允许其它已经存在的Shared
lock继续读数据库文件,但是不允许产生新的shared lock,这样做目的是为了防止写操作发生饿死情况。一旦所有的shared
lock完成操作,则pending lock升级到exclusive lock。
一旦得到
exclusive
lock,其它的进程就不能进行读操作,此时就可以把修改的页面写回数据库文件,但是通常OS都把结果暂时保存到磁盘缓存中,直到某个时刻才会真正把结果
写入磁盘。
//把所有的脏页面写入数据库
//到这里开始获取EXCLUSIVEQ锁,并将页面写回操作系统文件
static int pager_write_pagelist(PgHdr *pList){
Pager *pPager;
int rc;
if( pList==0 ) return SQLITE_OK;
pPager = pList->pPager;
/* At this point there may be either a RESERVED or EXCLUSIVE lock on the
** database file. If there is already an EXCLUSIVE lock, the following
** calls to sqlite3OsLock() are no-ops.
**
** Moving the lock from RESERVED to EXCLUSIVE actually involves going
** through an intermediate state PENDING. A PENDING lock prevents new
** readers from attaching to the database but is unsufficient for us to
** write. The idea of a PENDING lock is to prevent new readers from
** coming in while we wait for existing readers to clear.
**
** While the pager is in the RESERVED state, the original database file
** is unchanged and we can rollback without having to playback the
** journal into the original database file. Once we transition to
** EXCLUSIVE, it means the database file has been changed and any rollback
** will require a journal playback.
*/
//加EXCLUSIVE_LOCK锁
rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
if( rc!=SQLITE_OK ){
return rc;
}
while( pList ){
assert( pList->dirty );
rc = sqlite3OsSeek(pPager->fd, (pList->pgno-1)*(i64)pPager->pageSize);
if( rc ) return rc;
/* If there are dirty pages in the page cache with page numbers greater
** than Pager.dbSize, this means sqlite3pager_truncate() was called to
** make the file smaller (presumably by auto-vacuum code). Do not write
** any such pages to the file.
*/
if( pList->pgno<=pPager->dbSize ){
char *pData = CODEC2(pPager, PGHDR_TO_DATA(pList), pList->pgno, 6);
TRACE3("STORE %d page %d\n", PAGERID(pPager), pList->pgno);
//写入文件
rc = sqlite3OsWrite(pPager->fd, pData, pPager->pageSize);
TEST_INCR(pPager->nWrite);
}
#ifndef NDEBUG
else{
TRACE3("NOSTORE %d page %d\n", PAGERID(pPager), pList->pgno);
}
#endif
if( rc ) return rc;
//设置dirty
pList->dirty = 0;
#ifdef SQLITE_CHECK_PAGES
pList->pageHash = pager_pagehash(pList);
#endif
//指向下一个脏页面
pList = pList->pDirty;
}
return SQLITE_OK;
}
为了保证修改结果真正
写入磁盘,这一步必不要少。对于数据库存的完整性,这一步也是关键的一步。由于要进行实际的I/O操作,所以和第7步一样,将花费较多的时间。
//同步btree对应的数据库文件
//该函数返回之后,只需要提交写事务,删除日志文件
int sqlite3BtreeSync(Btree *p, const char *zMaster){
int rc = SQLITE_OK;
if( p->inTrans==TRANS_WRITE ){
BtShared *pBt = p->pBt;
Pgno nTrunc = 0;
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pBt->autoVacuum ){
rc = autoVacuumCommit(pBt, &nTrunc);
if( rc!=SQLITE_OK ){
return rc;
}
}
#endif
//调用pager进行sync
rc = sqlite3pager_sync(pBt->pPager, zMaster, nTrunc);
}
return rc;
}
//把pager所有脏页面写回文件
int sqlite3pager_sync(Pager *pPager, const char *zMaster, Pgno nTrunc){
int rc = SQLITE_OK;
TRACE4("DATABASE SYNC: File=%s zMaster=%s nTrunc=%d\n",
pPager->zFilename, zMaster, nTrunc);
/* If this is an in-memory db, or no pages have been written to, or this
** function has already been called, it is a no-op.
*/
//pager不处于PAGER_SYNCED状态,dirtyCache为1,
//则进行sync操作
if( pPager->state!=PAGER_SYNCED && !MEMDB && pPager->dirtyCache ){
PgHdr *pPg;
assert( pPager->journalOpen );
/* If a master journal file name has already been written to the
** journal file, then no sync is required. This happens when it is
** written, then the process fails to upgrade from a RESERVED to an
** EXCLUSIVE lock. The next time the process tries to commit the
** transaction the m-j name will have already been written.
*/
if( !pPager->setMaster ){
//pager修改计数
rc = pager_incr_changecounter(pPager);
if( rc!=SQLITE_OK ) goto sync_exit;
#ifndef SQLITE_OMIT_AUTOVACUUM
if( nTrunc!=0 ){
/* If this transaction has made the database smaller, then all pages
** being discarded by the truncation must be written to the journal
** file.
*/
Pgno i;
void *pPage;
int iSkip = PAGER_MJ_PGNO(pPager);
for( i=nTrunc+1; i<=pPager->origDbSize; i++ ){
if( !(pPager->aInJournal[i/8] & (1<<(i&7))) && i!=iSkip ){
rc = sqlite3pager_get(pPager, i, &pPage);
if( rc!=SQLITE_OK ) goto sync_exit;
rc = sqlite3pager_write(pPage);
sqlite3pager_unref(pPage);
if( rc!=SQLITE_OK ) goto sync_exit;
}
}
}
#endif
rc = writeMasterJournal(pPager, zMaster);
if( rc!=SQLITE_OK ) goto sync_exit;
//sync日志文件
rc = syncJournal(pPager);
if( rc!=SQLITE_OK ) goto sync_exit;
}
#ifndef SQLITE_OMIT_AUTOVACUUM
if( nTrunc!=0 ){
rc = sqlite3pager_truncate(pPager, nTrunc);
if( rc!=SQLITE_OK ) goto sync_exit;
}
#endif
/* Write all dirty pages to the database file */
pPg = pager_get_all_dirty_pages(pPager);
//把所有脏页面写回操作系统文件
rc = pager_write_pagelist(pPg);
if( rc!=SQLITE_OK ) goto sync_exit;
/* Sync the database file. */
//sync数据库文件
if( !pPager->noSync ){
rc = sqlite3OsSync(pPager->fd, 0);
}
pPager->state = PAGER_SYNCED;
}else if( MEMDB && nTrunc!=0 ){
rc = sqlite3pager_truncate(pPager, nTrunc);
}
sync_exit:
return rc;
}
发表评论
-
(转)Andriod是什么
2010-12-02 11:24 1613导读:Sans Serif是Google的 ... -
(转)SQLite入门与分析(六)---再谈SQLite的锁
2010-11-19 00:09 941写在前 面:SQLite封锁机制的实现需要底层文件系统的 ... -
(转)SQLite入门与分析(五)---Page Cache之并发控制
2010-11-19 00:05 1047写在前面:本节主要谈 ... -
(转)SQLite入门与分析(四)---Page Cache之事务处理(3)
2010-11-19 00:01 950Code 写在前面:由于 内容较多,所以断续没有写完的 ... -
(转)SQLite入门与分析(四)---Page Cache之事务处理(1)
2010-11-18 23:53 939写在前面:从本章开始,将对SQLite的每个模块进行讨论。 ... -
(转)SQLite入门与分析(三)---内核概述(2)
2010-11-18 23:48 1301写在前面:本节 是前 ... -
(转)SQLite入门与分析(三)---内核概述(1)
2010-11-18 23:41 796写在前面:从本 章开始, ... -
(转)SQLite入门与分析(二)---设计与概念(续)
2010-11-18 23:38 1039写在前面:本节 讨论事务,事务是DBMS最核心的技术之一 ... -
(转)SQLite入门与分析(二)---设计与概念
2010-11-18 23:35 790写在前面:谢谢各位的 ... -
(转)SQLite入门与分析(一)
2010-11-18 23:31 904写在前面:出于项目的 需要,最近打算对SQLite的内核 ... -
(转)深入研究B树索引(五)续
2010-11-18 15:10 9255.3 重建 B 树索引 ... -
(转)深入研究B树索引(五)
2010-11-18 15:07 11905. 重建 B ... -
(转)深入研究B树索引(四)续
2010-11-18 14:58 9114.2 B 树索引的对于删除( DEL ... -
(转)深入研究B树索引(三、四)
2010-11-18 14:44 7113. B 树索 ... -
(转)深入研究B树索引(二)
2010-11-18 14:20 7562. B 树索引的内部结构 ... -
(转)深入研究B树索引(一)
2010-11-18 14:12 1000摘要: 本文对B 树索引的结构、内部管理等方面做了一个全面 ... -
(转)B树、B-树、B+树、B*树都是什么
2010-11-17 23:46 671B 树 即二叉搜 ... -
画UML图时注意的几个原则(转)
2010-08-03 12:34 1622软件开发中,分析和设计时,文档的编写和思想的交流,经常要绘制各 ... -
你是个软件架构师吗?(转)
2010-07-14 11:11 661开发和架构的界限难以 ... -
(转)同曲异奏——高效能项目团队的五大特点
2010-03-29 00:24 856同曲异奏——高效能项 ...
相关推荐
在SQLite入门与分析(七)---浅谈SQLite的虚拟机.doc中,主要讲解了SQLite如何通过虚拟机执行SQL语句。SQLite的虚拟机,也称为VDBE(Virtual Database Engine),是SQLite的核心组件。它负责解析SQL语句,将其转化为一...
2. **sqlite3_analyzer.exe**:这是一个高级分析工具,用于对SQLite数据库进行深入的性能分析和内存使用分析。它可以提供关于数据库大小、索引效率、表空间使用情况等详细信息。这对于优化数据库性能、调整存储结构...
赠送jar包:sqlite-jdbc-3.15.1.jar; 赠送原API文档:sqlite-jdbc-3.15.1-javadoc.jar; 赠送源代码:sqlite-jdbc-3.15.1-sources.jar; 赠送Maven依赖信息文件:sqlite-jdbc-3.15.1.pom; 包含翻译后的API文档:...
sqlite-devel-3.7.17-8.el7.x86_64.rpm
对于那些不需要复杂事务处理和并发控制的场景,SQLite 提供了一个简单、高效的解决方案。然而,对于需要高度并发和复杂事务处理的应用,可能需要考虑使用更为强大的数据库系统,如 MySQL 或 PostgreSQL。
SQLite入门与分析(四)---Page Cache之事务处理.doc SQLite入门与分析(五)---Page Cache之并发控制.doc SQLite入门与分析(六)---再谈SQLite的锁.doc
SQLiteStudio是一款功能强大的SQLite数据库管理工具,专为Windows操作系统设计。SQLite本身是一个开源、轻量级的嵌入式SQL数据库引擎,广泛应用于各种桌面应用程序、移动设备和服务器环境,尤其适合那些对数据库性能...
通过分析这个源码包,开发者可以了解到如何在.NET项目中集成SQLite数据库,包括数据访问层的设计、事务处理、错误处理、查询构建等。同时,源码包还支持多种.NET版本和平台,使得开发者可以根据项目需求选择合适的...
"SQLite-1.0.66.0-setup" 标题表明这是一个关于SQLite数据库的安装程序,版本号为1.0.66.0。这个压缩包可能包含了用于在用户计算机上安装SQLite的必要文件。 描述中的重复信息“SQLite-1.0.66.0-setup”可能是一个...
总的来说,"sqlite-shell-linux-x86-3080900.zip" 是在Linux环境中管理和操作SQLite数据库的重要工具,它提供了一个直接与数据库交互的界面,使得数据管理变得简单易行。无论你是初学者还是经验丰富的开发者,熟悉并...
"sqlite-netFx451-setup-bundle-x86-2013-1.0.105.2.exe" 是一个针对VS2013的SQLite安装包,适用于x86架构的Windows系统,版本号为1.0.105.2。 1. **SQLite 介绍**: - SQLite是一个自包含、无服务器、零配置、...
sqlite-tools-win32-x86-3290000 是一个SQLite数据库工具在Windows 32位系统上的安装包或目录名称。SQLite是一个C库,提供了一个轻量级的磁盘文件数据库,不需要一个单独的服务器进程或操作系统(不需要配置、安装或...
在“sqlite-tools-linux-x86-3350400.zip”这个压缩包中,包含了以下关键的SQLite工具: 1. **sqlite3**: 这是SQLite的主要命令行接口。用户可以通过这个工具执行SQL语句,创建和管理数据库,查询数据,甚至进行...
1. **安装与运行**:解压"sqlite-shell-win32-x86-3080200.zip"到任意目录,然后找到sqlite3.exe,双击运行或在命令行中输入其路径启动。 2. **连接数据库**:在命令行中,输入`.open 数据库文件名`,例如`.open ...
“sqlite-autoconf”和“sqlite”标签则表明了它是自动配置的SQLite版本,与标准的SQLite库有关。这些标签有助于用户在搜索和管理资源时进行分类和识别。 总之,`sqlite-autoconf-3070800-arm.tar.gz` 包含了一个预...
2. `sqlite-shell-win32-x86-3071500.zip`:这是SQLite的命令行shell工具,适用于Windows操作系统且为32位架构。解压后,你可以直接运行这个可执行文件来直接操作SQLite数据库,进行数据查询、插入、更新和删除,...
2. sqlite-netFx46-static-binary-bundle-x64-2015-1.0.109.0.zip 和 sqlite-netFx46-static-binary-bundle-Win32-2015-1.0.109.0.zip:这些是捆绑版本,除了SQLite的库之外,还包含了系统运行时库,使得在没有安装...
4. **事务处理**:SQLite3支持ACID(原子性、一致性、隔离性和持久性)事务,确保数据的安全性。 5. **存储方式**:数据存储在单个文件中,便于备份和移动。 6. **性能**:尽管轻量级,但SQLite3在许多场景下表现出...
sqlite-netFx40-setup-bundle-x86-2010-1.0.113.0.exe