写在前面:从本章开始,将对SQLite的每个模块进行讨论。讨论的顺序按照我阅读SQLite的顺序来进行,由于项目的需要,以及时间关系,不能给出一个完整的计划,但是我会先讨论我认为比较重要的内容。本节讨论SQLite的事务处理技术,事务处理是DBMS中最关键的技术,对SQLite也一样,它涉及到并发控制,以及故障恢复,由于内容较多,分为两节。好了,下面进入正题。

本节通过一个具体的例子来分析SQLite原子提交的实现(基于Version 3.3.6的代码)。
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|

16|TableLock|0|2|1|episodes|00|

17|Goto|0|2|0||00|

1、初始状态(Initial State)
当一个数据库连接第一次打开时,状态如图所示。图中最右边(“Disk”标注)表示保存在存储设备中的内容。每个方框代表一个扇区。蓝色的块表示这个扇区保存了原始数据。图中中间区域是操作系统的磁盘缓冲区。开始的时候,这些缓存是还没有被使用,因此这些方框是空白的。图中左边区域显示SQLite用户进程的内存。因为这个数据库连接刚刚打开,所以还没有任何数据记录被读入,所以这些内存也是空的。

2、获取读锁(Acquiring A Read Lock)
在SQLite写数据库之前,它必须先从数据库中读取相关信息。比如,在插入新的数据时,SQLite会先从sqlite_master表中读取数据库模式(相当于数据字典),以便编译器对INSERT语句进行分析,确定数据插入的位置。
在进行读操作之前,必须先获取数据库的共享锁(shared lock),共享锁允许两个或更多的连接在同一时刻读取数据库。但是共享锁不允许其它连接对数据库进行写操作。
shared lock存在于操作系统磁盘缓存,而不是磁盘本身。文件锁的本质只是操作系统的内核数据结构,当操作系统崩溃或掉电时,这些内核数据也会随之消失。

3、读取数据
一旦得到shared lock,就可以进行读操作。如图所示,数据先由OS从磁盘读取到OS缓存,然后再由OS移到用户进程空间。一般来说,数据库文件分为很多页,而一次读操作只读取一小部分页面。如图,从8个页面读取3个页面。

4、获取Reserved Lock
在对数据进行修改操作之前,先要获取数据库文件的Reserved Lock,Reserved Lock和shared lock的相似之处在于,它们都允许其它进程对数据库文件进行读操作。Reserved Lock和Shared Lock可以共存,但是只能是一个Reserved Lock和多个Shared Lock——多个Reserved Lock不能共存。所以,在同一时刻,只能进行一个写操作。
Reserved Lock意味着当前进程(连接)想修改数据库文件,但是还没开始修改操作,所以其它的进程可以读数据库,但不能写数据库。

5、创建恢复日志(Creating A Rollback Journal File)
在对数据库进行写操作之前,SQLite先要创建一个单独的日志文件,然后把要修改的页面的原始数据写入日志。回滚日志包含一个日志头(图中的绿色)——记录数据库文件的原始大小。所以即使数据库文件大小改变了,我们仍知道数据库的原始大小。
从OS的角度来看,当一个文件创建时,大多数OS(Windows,Linux,Mac OS X)不会向磁盘写入数据,新创建的文件此时位于磁盘缓存中,之后才会真正写入磁盘。如图,日志文件位于OS磁盘缓存中,而不是位于磁盘。

上面 5步的代码的实现:

Code
//事务指令的实现
//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;
}

其实现过程如下图所示:

主要参考:http://www.sqlite.org/atomiccommit.html

转载于:https://www.cnblogs.com/hustcat/archive/2009/02/26/1398558.html

SQLite入门与分析(四)---Page Cache之事务处理(1)相关推荐

  1. SQLite入门与分析(四)---Page Cache之事务处理(3)

    写在前面:由于内容较多,所以断续没有写完的内容. 11.删除日志文件(Deleting The Rollback Journal) 一旦更改写入设备,日志文件将会被删除,这是事务真正提交的时刻.如果在 ...

  2. SQLite入门与分析(二)---设计与概念

    写在前面:谢谢各位的关注,没想到会有这么多人关注.高兴的同时,也感到压力,因为我接触SQLite也就几天,也没在实际开发中用过,只是最近项目的需求才来研究它,所以我很担心自己的文章是否会有错误,误导别 ...

  3. SQLite 入门教程(四)增删改查,有讲究

    增删改查操作,其中增删改操作被称为数据操作语言 DML,相对来说简单一点. 查操作相对来说复杂一点,涉及到很多子句,所以这篇先讲增删改操作,以例子为主,后面再讲查操作. 一.插入数据 INSERT I ...

  4. ArcGIS for Desktop入门教程_第四章_入门案例分析 - ArcGIS知乎-新一代ArcGIS问答社区...

    原文:ArcGIS for Desktop入门教程_第四章_入门案例分析 - ArcGIS知乎-新一代ArcGIS问答社区 1 入门案例分析 在第一章里,我们已经对ArcGIS系列软件的体系结构有了一 ...

  5. WebKit 内核源码分析 (四)

    本文介绍 WebCore 中 Loader 模块是如何加载资源的,分主资源和派生资源分析 loader 模块的类关系. 关键词: WebKit,Loader,Network,ResouceLoader ...

  6. 和linux关系_Linux内核Page Cache和Buffer Cache关系及演化历史

    在我们进行数据持久化,对文件内容进行落盘处理时,我们时常会使用fsync操作,该操作会将文件关联的脏页(dirty page)数据(实际文件内容及元数据信息)一同写回磁盘.这里提到的脏页(dirty ...

  7. 网络缓存 峰值 linux,Linux Page Cache调优在Kafka中的应用

    本文首发于 vivo互联网技术 微信公众号 链接: 作者:Yang Yijun 本文主要描述Linux Page Cache优化的背景.Page Cache的基本概念.列举之前针对Kafka的 IO ...

  8. Linux的Page Cache

    1. Page Cache 何为Page Cache 为了了解Page Cache我们可以看一下Linux的文件I/O系统 从图中可以看出,Page Cache是由Linux内核进行管理的,而且通过m ...

  9. Page cache和Buffer cache[转1]

    http://www.cnblogs.com/mydomain/archive/2013/02/24/2924707.html Page cache实际上是针对文件系统的,是文件的缓存,在文件层面上的 ...

最新文章

  1. spring2.5+struts2+hibernate+mysql
  2. 【PAT - 甲级1012】The Best Rank (25分)
  3. html div element,你能在TypeScript中扩展HTMLDivElement吗?
  4. Linux初级入门(第一次作业)
  5. 互联网公司上演反腐风暴;GitHub CEO 对断供表示无能为力;程序员面试锦集| 开发者周刊...
  6. 如何在 Apple TV 上获得电影推荐?
  7. 博客前端模板源码(力荐)
  8. dedecms---一个简单酷站的构建及解析
  9. Android常用库和插件避免重复造轮子(持续更新)
  10. 绝地求生2020服务器维护中,绝地求生2020最新维护公告几点开服?3月18日更新内容一览...
  11. NOIP2015跳石头【二分答案(最小值最大化) | 贪心】
  12. 扔掉信用卡的10个理由
  13. 蚂蚁金服 ant design 中下载axure 菜单组件库
  14. Numpy 最大值与最大值索引函数+random总结
  15. html怎么设计为中文字体,CSS font-family中文字体设置方法
  16. 日志收集Agent方案和比较
  17. 计算机无法检查出来的错误,Win7系统电脑显卡出现错误时怎么检测(图文)
  18. 计算机辅助医疗器械,计算机辅助外科手术中医疗机器人技术研究综述
  19. 编程(从入门到入土)8
  20. pygame模块参数汇总

热门文章

  1. Picasso源码阅读笔记六
  2. 开源中国iOS客户端学习——(七)MBProgressHUD特效
  3. npm配置镜像、设置代理
  4. IEC61850的Read请求报文件MMS PDU解码
  5. 学习 SCCM 2012 的思路
  6. 台大李宏毅Machine Learning 2017Fall学习笔记 (5)Classification: Probabilistic Generative Model
  7. Ubuntu 14.04安装Python3
  8. iOS中的KeyChain的用途
  9. 二维码扫描ZXing简化
  10. 让win7任务栏资源管理器默认打开的“计算机”而不是“库”文件夹