- 浏览: 29526 次
- 性别:
- 来自: 广州
最新评论
写在前面:从本章开始,将对SQLite的每个模块进行讨论。讨论的顺序按照我阅读SQLite的顺序来进行,由于项目的
需要,以及时间关系,不能给出一个完整的计划,但是我会先讨论我认为比较重要的内容。本节讨论SQLite的事务处理技术,事务处理是DBMS中最关键的
技术,对SQLite也一样,它涉及到并发控制,以及故障恢复,由于内容较多,分为两节。好了,下面进入正题。
本节通过一个具体的例子来分析SQLite原子提交的实现(基于Version
3.3.6的代码)。 16|TableLock|0|2|1|episodes|00|
17|Goto|0|2|0||00|
1、
初始状态(Initial State)
2
、获取读锁(Acquiring A Read Lock) 3、读取数据
4、获取Reserved Lock 5、创建恢复日志(Creating A
Rollback Journal File)
上面 5步的代码的实现:
其实现过程如下图所示:
主要参考:
http://www.sqlite.org/atomiccommit.html
CREATE TABLE episodes( id integer primary key,name text,
cid int) ;
插入一条记录:insert into episodes(name,cid) values("cat",1) ;
它
经过编译器处理后生成的虚拟机代码如下:
sqlite> explain insert into episodes(name,cid)
values("cat",1);
0|Trace|0|0|0|explain insert into
episodes(name,cid) values("cat",1);|00|
1|Goto|0|12|0||00|
2|SetNumColumns|0|3|0||00|
3|OpenWrite|0|2|0||00|
4|NewRowid|0|2|0||00|
5|Null|0|3|0||00|
6|String8|0|4|0|cat|00|
7|Integer|1|5|0||00|
8|MakeRecord|3|3|6|dad|00|
9|Insert|0|6|2|episodes|0b|
10|Close|0|0|0||00|
11|Halt|0|0|0||00|
12|Transaction|0|1|0||00|
13|VerifyCookie|0|1|0||00|
14|Transaction|1|1|0||00|
15|VerifyCookie|1|0|0||00|
当一个数据库连接第一次打开时,状态如图所示。图中最右边(“Disk”标
注)表示保存在存储设备中的内容。每个方框代表一个扇区。蓝色的块表示这个扇区保存了原始数据。图中中间区域是操作系统的磁盘缓冲区。开始的时候,这些缓
存是还没有被使用,因此这些方框是空白的。图中左边区域显示SQLite用户进程的内存。因为这个数据库连接刚刚打开,所以还没有任何数据记录被读入,所
以这些内存也是空的。
在SQLite写数据库之
前,它必须先从数据库中读取相关信息。比如,在插入新的数据时,SQLite会先从sqlite_master表中读取数据库模式(相当于数据字典),以
便编译器对INSERT语句进行分析,确定数据插入的位置。
在进行读操作之前,必须先获取数据库的共享锁(shared
lock),共享锁允许两个或更多的连接在同一时刻读取数据库。但是共享锁不允许其它连接对数据库进行写操作。
shared
lock存在于操作系统磁盘缓存,而不是磁盘本身。文件锁的本质只是操作系统的内核数据结构,当操作系统崩溃或掉电时,这些内核数据也会随之消失。
一旦得到
shared
lock,就可以进行读操作。如图所示,数据先由OS从磁盘读取到OS缓存,然后再由OS移到用户进程空间。一般来说,数据库文件分为很多页,而一次读操
作只读取一小部分页面。如图,从8个页面读取3个页面。
在
对数据进行修改操作之前,先要获取数据库文件的Reserved Lock,Reserved Lock和shared
lock的相似之处在于,它们都允许其它进程对数据库文件进行读操作。Reserved Lock和Shared
Lock可以共存,但是只能是一个Reserved Lock和多个Shared Lock——多个Reserved
Lock不能共存。所以,在同一时刻,只能进行一个写操作。
Reserved
Lock意味着当前进程(连接)想修改数据库文件,但是还没开始修改操作,所以其它的进程可以读数据库,但不能写数据库。
在对数据库进行写操作之前,SQLite先要创建一个单独的日志文件,然后把要修改
的页面的原始数据写入日志。回滚日志包含一个日志头(图中的绿色)——记录数据库文件的原始大小。所以即使数据库文件大小改变了,我们仍知道数据库的原始
大小。
从OS的角度来看,当一个文件创建时,大多数OS(Windows,Linux,Mac OS
X)不会向磁盘写入数据,新创建的文件此时位于磁盘缓存中,之后才会真正写入磁盘。如图,日志文件位于OS磁盘缓存中,而不是位于磁盘。
//事务指令的实现
//p1为数据库文件的索引号---0为main database;1为 temporary tables使用的文件
//p2 不为0,一个写事务开始
case OP_Transaction: {
//数据库的索引号
int i = pOp->p1;
//指向数据库对应的btree
Btree *pBt;
assert( i>=0 && i<db->nDb );
assert( (p->btreeMask & (1<<i))!=0 );
//设置btree指针
pBt = db->aDb[i].pBt;
if( pBt ){
//从这里btree开始事务,主要给文件加锁,并设置btree事务状态
rc = sqlite3BtreeBeginTrans(pBt, pOp->p2);
if( rc==SQLITE_BUSY ){
p->pc = pc;
p->rc = rc = SQLITE_BUSY;
goto vdbe_return;
}
if( rc!=SQLITE_OK && rc!=SQLITE_READONLY /* && rc!=SQLITE_BUSY */ ){
goto abort_due_to_error;
}
}
break;
}
//开始一个事务,如果第二个参数不为0,则一个写事务开始,否则是一个读事务
//如果wrflag>=2,一个exclusive事务开始,此时别的连接不能访问数据库
int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
BtShared *pBt = p->pBt;
int rc = SQLITE_OK;
btreeIntegrity(p);
/* If the btree is already in a write-transaction, or it
** is already in a read-transaction and a read-transaction
** is requested, this is a no-op.
*/
//如果b-tree处于一个写事务;或者处于一个读事务,一个读事务又请求,则返回 SQLITE_OK
if( p->inTrans==TRANS_WRITE || (p->inTrans==TRANS_READ && !wrflag) ){
return SQLITE_OK;
}
/* Write transactions are not possible on a read-only database */
//写事务不能访问只读数据库
if( pBt->readOnly && wrflag ){
return SQLITE_READONLY;
}
/* If another database handle has already opened a write transaction
** on this shared-btree structure and a second write transaction is
** requested, return SQLITE_BUSY.
*/
//如果数据库已存在一个写事务,则该写事务请求时返回SQLITE_BUSY
if( pBt->inTransaction==TRANS_WRITE && wrflag ){
return SQLITE_BUSY;
}
do {
//如果数据库对应btree的第一个页面还没读进内存
//则把该页面读进内存,数据库也相应的加read lock
if( pBt->pPage1==0 ){
//加read lock,并读页面到内存
rc = lockBtree(pBt);
}
if( rc==SQLITE_OK && wrflag ){
//对数据库文件加RESERVED_LOCK锁
rc = sqlite3pager_begin(pBt->pPage1->aData, wrflag>1);
if( rc==SQLITE_OK ){
rc = newDatabase(pBt);
}
}
if( rc==SQLITE_OK ){
if( wrflag ) pBt->inStmt = 0;
}else{
unlockBtreeIfUnused(pBt);
}
}while( rc==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE &&
sqlite3InvokeBusyHandler(pBt->pBusyHandler) );
if( rc==SQLITE_OK ){
if( p->inTrans==TRANS_NONE ){
//btree的事务数加1
pBt->nTransaction++;
}
//设置btree事务状态
p->inTrans = (wrflag?TRANS_WRITE:TRANS_READ);
if( p->inTrans>pBt->inTransaction ){
pBt->inTransaction = p->inTrans;
}
}
btreeIntegrity(p);
return rc;
}
/*
**获取数据库的写锁,发生以下情况时去除写锁:
** * sqlite3pager_commit() is called.
** * sqlite3pager_rollback() is called.
** * sqlite3pager_close() is called.
** * sqlite3pager_unref() is called to on every outstanding page.
** pData 指向数据库的打开的页面,此时并不修改,仅仅只是获取
** 相应的pager,检查它是否处于read-lock状态。
**如果打开的不是临时文件,则打开日志文件.
**如果数据库已经处于写状态,则do nothing
*/
int sqlite3pager_begin(void *pData, int exFlag){
PgHdr *pPg = DATA_TO_PGHDR(pData);
Pager *pPager = pPg->pPager;
int rc = SQLITE_OK;
assert( pPg->nRef>0 );
assert( pPager->state!=PAGER_UNLOCK );
//pager已经处于share状态
if( pPager->state==PAGER_SHARED ){
assert( pPager->aInJournal==0 );
if( MEMDB ){
pPager->state = PAGER_EXCLUSIVE;
pPager->origDbSize = pPager->dbSize;
}else{
//对文件加 RESERVED_LOCK
rc = sqlite3OsLock(pPager->fd, RESERVED_LOCK);
if( rc==SQLITE_OK ){
//设置pager的状态
pPager->state = PAGER_RESERVED;
if( exFlag ){
rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
}
}
if( rc!=SQLITE_OK ){
return rc;
}
pPager->dirtyCache = 0;
TRACE2("TRANSACTION %d\n", PAGERID(pPager));
//使用日志,不是临时文件,则打开日志文件
if( pPager->useJournal && !pPager->tempFile ){
//为pager打开日志文件,pager应该处于RESERVED或EXCLUSIVE 状态
//会向日志文件写入header
rc = pager_open_journal(pPager);
}
}
}
return rc;
}
//创建日志文件,pager应该处于RESERVED或EXCLUSIVE状态
static int pager_open_journal(Pager *pPager){
int rc;
assert( !MEMDB );
assert( pPager->state>=PAGER_RESERVED );
assert( pPager->journalOpen==0 );
assert( pPager->useJournal );
assert( pPager->aInJournal==0 );
sqlite3pager_pagecount(pPager);
//日志文件页面位图
pPager->aInJournal = sqliteMalloc( pPager->dbSize/8 + 1 );
if( pPager->aInJournal==0 ){
rc = SQLITE_NOMEM;
goto failed_to_open_journal;
}
//打开日志文件
rc = sqlite3OsOpenExclusive(pPager->zJournal, &pPager->jfd,
pPager->tempFile);
//日志文件的位置指针
pPager->journalOff = 0;
pPager->setMaster = 0;
pPager->journalHdr = 0;
if( rc!=SQLITE_OK ){
goto failed_to_open_journal;
}
/*一般来说,os此时创建的文件位于磁盘缓存,并没有实际
**存在于磁盘, 下面三个操作就是为了把结果写入磁盘,而对于
**windows系统来说,并没有提供相应API,所以实际上没有意义.
*/
//fullSync操作对windows没有意义
sqlite3OsSetFullSync(pPager->jfd, pPager->full_fsync);
sqlite3OsSetFullSync(pPager->fd, pPager->full_fsync);
/* Attempt to open a file descriptor for the directory that contains a file.
**This file descriptor can be used to fsync() the directory
**in order to make sure the creation of a new file is actually written to disk.
*/
sqlite3OsOpenDirectory(pPager->jfd, pPager->zDirectory);
pPager->journalOpen = 1;
pPager->journalStarted = 0;
pPager->needSync = 0;
pPager->alwaysRollback = 0;
pPager->nRec = 0;
if( pPager->errCode ){
rc = pPager->errCode;
goto failed_to_open_journal;
}
pPager->origDbSize = pPager->dbSize;
//写入日志文件的header---24个字节
rc = writeJournalHdr(pPager);
if( pPager->stmtAutoopen && rc==SQLITE_OK ){
rc = sqlite3pager_stmt_begin(pPager);
}
if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM ){
rc = pager_unwritelock(pPager);
if( rc==SQLITE_OK ){
rc = SQLITE_FULL;
}
}
return rc;
failed_to_open_journal:
sqliteFree(pPager->aInJournal);
pPager->aInJournal = 0;
if( rc==SQLITE_NOMEM ){
/* If this was a malloc() failure, then we will not be closing the pager
** file. So delete any journal file we may have just created. Otherwise,
** the system will get confused, we have a read-lock on the file and a
** mysterious journal has appeared in the filesystem.
*/
sqlite3OsDelete(pPager->zJournal);
}else{
sqlite3OsUnlock(pPager->fd, NO_LOCK);
pPager->state = PAGER_UNLOCK;
}
return rc;
}
/*写入日志文件头
**journal header的格式如下:
** - 8 bytes: 标 志日志文件的魔数
** - 4 bytes: 日志文件中记录数
** - 4 bytes: Random number used for page hash.
** - 4 bytes: 原 来数据库的大小(kb)
** - 4 bytes: 扇区大小512byte
*/
static int writeJournalHdr(Pager *pPager){
//日志文件头
char zHeader[sizeof(aJournalMagic)+16];
int rc = seekJournalHdr(pPager);
if( rc ) return rc;
pPager->journalHdr = pPager->journalOff;
if( pPager->stmtHdrOff==0 ){
pPager->stmtHdrOff = pPager->journalHdr;
}
//设置文件指针指向header之后
pPager->journalOff += JOURNAL_HDR_SZ(pPager);
/* FIX ME:
**
** Possibly for a pager not in no-sync mode, the journal magic should not
** be written until nRec is filled in as part of next syncJournal().
**
** Actually maybe the whole journal header should be delayed until that
** point. Think about this.
*/
memcpy(zHeader, aJournalMagic, sizeof(aJournalMagic));
/* The nRec Field. 0xFFFFFFFF for no-sync journals. */
put32bits(&zHeader[sizeof(aJournalMagic)], pPager->noSync ? 0xffffffff : 0);
/* The random check-hash initialiser */
sqlite3Randomness(sizeof(pPager->cksumInit), &pPager->cksumInit);
put32bits(&zHeader[sizeof(aJournalMagic)+4], pPager->cksumInit);
/* The initial database size */
put32bits(&zHeader[sizeof(aJournalMagic)+8], pPager->dbSize);
/* The assumed sector size for this process */
put32bits(&zHeader[sizeof(aJournalMagic)+12], pPager->sectorSize);
//写入文件头
rc = sqlite3OsWrite(pPager->jfd, zHeader, sizeof(zHeader));
/* The journal header has been written successfully. Seek the journal
** file descriptor to the end of the journal header sector.
*/
if( rc==SQLITE_OK ){
rc = sqlite3OsSeek(pPager->jfd, pPager->journalOff-1);
if( rc==SQLITE_OK ){
rc = sqlite3OsWrite(pPager->jfd, "\000", 1);
}
}
return rc;
}
发表评论
-
(转)Andriod是什么
2010-12-02 11:24 1612导读:Sans Serif是Google的 ... -
(转)SQLite入门与分析(六)---再谈SQLite的锁
2010-11-19 00:09 941写在前 面:SQLite封锁机制的实现需要底层文件系统的 ... -
(转)SQLite入门与分析(五)---Page Cache之并发控制
2010-11-19 00:05 1045写在前面:本节主要谈 ... -
(转)SQLite入门与分析(四)---Page Cache之事务处理(3)
2010-11-19 00:01 950Code 写在前面:由于 内容较多,所以断续没有写完的 ... -
(转)SQLite入门与分析(四)---Page Cache之事务处理(2)
2010-11-18 23:57 1140写在前面:个人认为pager层是SQLite实现最为核心的 ... -
(转)SQLite入门与分析(三)---内核概述(2)
2010-11-18 23:48 1300写在前面:本节 是前 ... -
(转)SQLite入门与分析(三)---内核概述(1)
2010-11-18 23:41 795写在前面:从本 章开始, ... -
(转)SQLite入门与分析(二)---设计与概念(续)
2010-11-18 23:38 1039写在前面:本节 讨论事务,事务是DBMS最核心的技术之一 ... -
(转)SQLite入门与分析(二)---设计与概念
2010-11-18 23:35 789写在前面:谢谢各位的 ... -
(转)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语句,将其转化为一...
"sqlite-tools-win-x64-3440200.zip"这个压缩包包含了三个主要的SQLite实用工具,它们分别是sqlite3_analyzer.exe、sqlite3.exe和sqldiff.exe。 1. **sqlite3.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
通过分析这个源码包,开发者可以了解到如何在.NET项目中集成SQLite数据库,包括数据访问层的设计、事务处理、错误处理、查询构建等。同时,源码包还支持多种.NET版本和平台,使得开发者可以根据项目需求选择合适的...
SQLiteStudio是一款功能强大的SQLite数据库管理工具,专为Windows操作系统设计。SQLite本身是一个开源、轻量级的嵌入式SQL数据库引擎,广泛应用于各种桌面应用程序、移动设备和服务器环境,尤其适合那些对数据库性能...
总的来说,"sqlite-shell-linux-x86-3080900.zip" 是在Linux环境中管理和操作SQLite数据库的重要工具,它提供了一个直接与数据库交互的界面,使得数据管理变得简单易行。无论你是初学者还是经验丰富的开发者,熟悉并...
"SQLite-1.0.66.0-setup" 标题表明这是一个关于SQLite数据库的安装程序,版本号为1.0.66.0。这个压缩包可能包含了用于在用户计算机上安装SQLite的必要文件。 描述中的重复信息“SQLite-1.0.66.0-setup”可能是一个...
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` 包含了一个预...
1. `sqlite-jdbc-3.7.15-M1.jar`:这是一个Java类库文件,包含了SQLite JDBC驱动的所有必要组件。要使用它,你需要将这个JAR文件添加到Java项目的类路径中。当你的Java程序运行时,它可以提供与SQLite数据库的连接,...
"sqlite-netFx451-setup-bundle-x86-2013-1.0.105.2.exe" 是一个针对VS2013的SQLite安装包,适用于x86架构的Windows系统,版本号为1.0.105.2。 1. **SQLite 介绍**: - SQLite是一个自包含、无服务器、零配置、...
### 嵌入式数据库SQLite入门与分析 #### 一、SQLite简介 SQLite是一种轻量级的嵌入式关系型数据库管理系统,最初于2000年由D.Richard Hipp开发并发布。作为一种嵌入式数据库,SQLite与其他传统数据库管理系统(如...
4. **事务处理**:SQLite3支持ACID(原子性、一致性、隔离性和持久性)事务,确保数据的安全性。 5. **存储方式**:数据存储在单个文件中,便于备份和移动。 6. **性能**:尽管轻量级,但SQLite3在许多场景下表现出...
sqlite-netFx40-setup-bundle-x86-2010-1.0.113.0.exe