`

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

阅读更多

11、删除日志文件(Deleting The Rollback Journal)
一旦更改写入设备,日志文件将会被删除,这是事务真正提交的时刻。如果在这之前系统发生崩溃,就会进行恢复处理,使得数据库和没发生改变一样;如果在这之后系统发生崩溃,表明所有的更改都已经写入磁盘。SQLite就是根据日志存在情况决定是否对数据库进行恢复处理。

删除文件本质上不是一个原子操作,但是从用户进程的角度来看是一个原子操作,所以一个事务看起来是一个原子操作。
在许多系统中,删除文件也是一个高代价的操作。作为优化,SQLite可以配置成把日志文件的长度截为0或者把日志文件头清零。

 

12、释放锁(Releasing The Lock)
作为原子提交的最后一步,释放排斥锁使得其它进程可以开始访问数据库了。
下图中,我们指明了当锁被释放的时候用户空间所拥有的信息已经被清空了.对于老版本的SQLite你可这么认为。但最新的SQLite会保存些用户空间的缓存不会被清空—万一下一个事务开始的时候,这些数据刚好可以用上呢。重新利用这些内存要比再次从操作系统磁盘缓存或者硬盘中读取要来得轻松与快捷得多,何乐而不为呢?在再次使用这些数据之前,我们必须先取得一个共享锁,同时我们还不得不去检查一下,保证还没有其他进程在我们拥有共享锁之前对数据库文件进行了修改。数据库文件的第一页中有一个计数器,数据库文件每做一次修改,这个计数器就会增长一下。我们可以通过检查这个计数器就可得知是否有其他进程修改过数据库文件。如果数据库文件已经被修改过了,那么用户内存空间的缓存就不得不清空,并重新读入。大多数情况下,这种情况不大会发生,因此用户空间的内存缓存将是有效的,这对于性能提高来说作用是显著的。

 

以上两步是在sqlite3BtreeCommit()---btree.c函数中实现的。

代码如下:

 

Code

下图可进一步描述该过程:

 

最后来看看sqlite3BtreeSync()和sqlite3BtreeCommit()是如何被调用的。

一般来说,事务提交方式为自动提交的话,在虚拟机中的OP_Halt指令实现提交事务,相关代码如下:

 


//虚拟机停机指令
case OP_Halt: {            /* no-push */
  p
->pTos = pTos;
  p
->rc = pOp->p1;
  p
->pc = pc;
  p
->errorAction = pOp->p2;
  
if( pOp->p3 ){
    sqlite3SetString(
&p->zErrMsg, pOp->p3, (char*)0);
  }
  
//设置虚拟机状态SQLITE_MAGIC_RUN 为 SQLITE_MAGIC_HALT,
  
//并提交事务
  rc = sqlite3VdbeHalt(p);
  assert( rc
==SQLITE_BUSY || rc==SQLITE_OK );
  
if( rc==SQLITE_BUSY ){
    p
->rc = SQLITE_BUSY;
    
return SQLITE_BUSY;
  }
  
return p->rc ? SQLITE_ERROR : SQLITE_DONE;
}

//当虚拟机要停机时,调用该函数,如果VDBE改变了数据库且为自动
//提交模式,则提交这些改变
int sqlite3VdbeHalt(Vdbe *p){
  sqlite3 
*db = p->db;
  
int i;
  
int (*xFunc)(Btree *pBt) = 0;  /* Function to call on each btree backend */
  
int isSpecialError;            /* Set to true if SQLITE_NOMEM or IOERR */

  
/* This function contains the logic that determines if a statement or
  ** transaction will be committed or rolled back as a result of the
  ** execution of this virtual machine. 
  **
  ** Special errors:
  **
  **     If an SQLITE_NOMEM error has occured in a statement that writes to
  **     the database, then either a statement or transaction must be rolled
  **     back to ensure the tree-structures are in a consistent state. A
  **     statement transaction is rolled back if one is open, otherwise the
  **     entire transaction must be rolled back.
  **
  **     If an SQLITE_IOERR error has occured in a statement that writes to
  **     the database, then the entire transaction must be rolled back. The
  **     I/O error may have caused garbage to be written to the journal 
  **     file. Were the transaction to continue and eventually be rolled 
  **     back that garbage might end up in the database file.
  **     
  **     In both of the above cases, the Vdbe.errorAction variable is 
  **     ignored. If the sqlite3.autoCommit flag is false and a transaction
  **     is rolled back, it will be set to true.
  **
  ** Other errors:
  **
  ** No error:
  **
  
*/

  
if( sqlite3MallocFailed() ){
    p
->rc = SQLITE_NOMEM;
  }
  
if( p->magic!=VDBE_MAGIC_RUN ){
    
/* Already halted.  Nothing to do. */
    assert( p
->magic==VDBE_MAGIC_HALT );
    
return SQLITE_OK;
  }
  
//释放虚拟机中所有的游标
  closeAllCursors(p);
  checkActiveVdbeCnt(db);

  
/* No commit or rollback needed if the program never started */
  
if( p->pc>=0 ){

    
/* Check for one of the special errors - SQLITE_NOMEM or SQLITE_IOERR */
    isSpecialError 
= ((p->rc==SQLITE_NOMEM || p->rc==SQLITE_IOERR)?1:0);
    
if( isSpecialError ){
      
/* This loop does static analysis of the query to see which of the
      ** following three categories it falls into:
      **
      **     Read-only
      **     Query with statement journal
      **     Query without statement journal
      **
      ** We could do something more elegant than this static analysis (i.e.
      ** store the type of query as part of the compliation phase), but 
      ** handling malloc() or IO failure is a fairly obscure edge case so 
      ** this is probably easier. Todo: Might be an opportunity to reduce 
      ** code size a very small amount though
      
*/
      
int isReadOnly = 1;
      
int isStatement = 0;
      assert(p
->aOp || p->nOp==0);
      
for(i=0; i<p->nOp; i++){ 
        
switch( p->aOp[i].opcode ){
          
case OP_Transaction:
            isReadOnly 
= 0;
            
break;
          
case OP_Statement:
            isStatement 
= 1;
            
break;
        }
      }
  
      
/* If the query was read-only, we need do no rollback at all. Otherwise,
      ** proceed with the special handling.
      
*/
      
if!isReadOnly ){
        
if( p->rc==SQLITE_NOMEM && isStatement ){
          xFunc 
= sqlite3BtreeRollbackStmt;
        }
else{
          
/* We are forced to roll back the active transaction. Before doing
          ** so, abort any other statements this handle currently has active.
          
*/
          sqlite3AbortOtherActiveVdbes(db, p);
          sqlite3RollbackAll(db);
          db
->autoCommit = 1;
        }
      }
    }
  
    
/* If the auto-commit flag is set and this is the only active vdbe, then
    ** we do either a commit or rollback of the current transaction. 
    **
    ** Note: This block also runs if one of the special errors handled 
    ** above has occured. 
    
*/
    
//如果自动提交事务,则提交事务
    if( db->autoCommit && db->activeVdbeCnt==1 ){
      
if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){
    
/* The auto-commit flag is true, and the vdbe program was 
        ** successful or hit an 'OR FAIL' constraint. This means a commit 
        ** is required.
        
*/
        
//提交事务
        int rc = vdbeCommit(db);
        
if( rc==SQLITE_BUSY ){
          
return SQLITE_BUSY;
        }
else if( rc!=SQLITE_OK ){
          p
->rc = rc;
          sqlite3RollbackAll(db);
        }
else{
          sqlite3CommitInternalChanges(db);
        }
      }
else{
        sqlite3RollbackAll(db);
      }
    }
else if!xFunc ){
      
if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){
        xFunc 
= sqlite3BtreeCommitStmt;
      }
else if( p->errorAction==OE_Abort ){
        xFunc 
= sqlite3BtreeRollbackStmt;
      }
else{
        sqlite3AbortOtherActiveVdbes(db, p);
        sqlite3RollbackAll(db);
        db
->autoCommit = 1;
      }
    }
  
    
/* If xFunc is not NULL, then it is one of sqlite3BtreeRollbackStmt or
    ** sqlite3BtreeCommitStmt. Call it once on each backend. If an error occurs
    ** and the return code is still SQLITE_OK, set the return code to the new
    ** error value.
    
*/
    assert(
!xFunc ||
      xFunc
==sqlite3BtreeCommitStmt ||
      xFunc
==sqlite3BtreeRollbackStmt
    );
    
for(i=0; xFunc && i<db->nDb; i++){ 
      
int rc;
      Btree 
*pBt = db->aDb[i].pBt;
      
if( pBt ){
        rc 
= xFunc(pBt);
        
if( rc && (p->rc==SQLITE_OK || p->rc==SQLITE_CONSTRAINT) ){
          p
->rc = rc;
          sqlite3SetString(
&p->zErrMsg, 0);
        }
      }
    }
  
    
/* If this was an INSERT, UPDATE or DELETE and the statement was committed, 
    ** set the change counter. 
    
*/
    
if( p->changeCntOn && p->pc>=0 ){
      
if!xFunc || xFunc==sqlite3BtreeCommitStmt ){
        sqlite3VdbeSetChanges(db, p
->nChange);
      }
else{
        sqlite3VdbeSetChanges(db, 
0);
      }
      p
->nChange = 0;
    }
  
    
/* Rollback or commit any schema changes that occurred. */
    
if( p->rc!=SQLITE_OK && db->flags&SQLITE_InternChanges ){
      sqlite3ResetInternalSchema(db, 
0);
      db
->flags = (db->flags | SQLITE_InternChanges);
    }
  }

  
/* We have successfully halted and closed the VM.  Record this fact. */
  
if( p->pc>=0 ){
    db
->activeVdbeCnt--;
  }
  p
->magic = VDBE_MAGIC_HALT;
  checkActiveVdbeCnt(db);

  
return SQLITE_OK;
}

//提交事务,主要调用:
//sqlite3BtreeSync()---同步btree, sqlite3BtreeCommit()---提交事务
static int vdbeCommit(sqlite3 *db){
  
int i;
  
int nTrans = 0;  /* Number of databases with an active write-transaction */
  
int rc = SQLITE_OK;
  
int needXcommit = 0;

  
for(i=0; i<db->nDb; i++){ 
    Btree 
*pBt = db->aDb[i].pBt;
    
if( pBt && sqlite3BtreeIsInTrans(pBt) ){
      needXcommit 
= 1;
      
if( i!=1 ) nTrans++;
    }
  }

  
/* If there are any write-transactions at all, invoke the commit hook */
  
if( needXcommit && db->xCommitCallback ){
    sqlite3SafetyOff(db);
    rc 
= db->xCommitCallback(db->pCommitArg);
    sqlite3SafetyOn(db);
    
if( rc ){
      
return SQLITE_CONSTRAINT;
    }
  }

  
/* The simple case - no more than one database file (not counting the
  ** TEMP database) has a transaction active.   There is no need for the
  ** master-journal.
  **
  ** If the return value of sqlite3BtreeGetFilename() is a zero length
  ** string, it means the main database is :memory:.  In that case we do
  ** not support atomic multi-file commits, so use the simple case then
  ** too.
  
*/
  
//简单的情况,只有一个数据库文件,不需要master-journal
  if0==strlen(sqlite3BtreeGetFilename(db->aDb[0].pBt)) || nTrans<=1 ){
    
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ 
      Btree 
*pBt = db->aDb[i].pBt;
      
if( pBt ){
          
//同步btree
        rc = sqlite3BtreeSync(pBt, 0);
      }
    }

    
/* Do the commit only if all databases successfully synced */
    
//commite事务
    if( rc==SQLITE_OK ){
      
for(i=0; i<db->nDb; i++){
        Btree 
*pBt = db->aDb[i].pBt;
        
if( pBt ){
          sqlite3BtreeCommit(pBt);
        }
      }
    }
  }

  
/* The complex case - There is a multi-file write-transaction active.
  ** This requires a master journal file to ensure the transaction is
  ** committed atomicly.
  
*/
#ifndef SQLITE_OMIT_DISKIO
  
else{
    
int needSync = 0;
    
char *zMaster = 0;   /* File-name for the master journal */
    
char const *zMainFile = sqlite3BtreeGetFilename(db->aDb[0].pBt);
    OsFile 
*master = 0;

    
/* Select a master journal file name */
    
do {
      u32 random;
      sqliteFree(zMaster);
      sqlite3Randomness(
sizeof(random), &random);
      zMaster 
= sqlite3MPrintf("%s-mj%08X", zMainFile, random&0x7fffffff);
      
if!zMaster ){
        
return SQLITE_NOMEM;
      }
    }
while( sqlite3OsFileExists(zMaster) );

    
/* Open the master journal. */
    rc 
= sqlite3OsOpenExclusive(zMaster, &master, 0);
    
if( rc!=SQLITE_OK ){
      sqliteFree(zMaster);
      
return rc;
    }
 
    
/* Write the name of each database file in the transaction into the new
    ** master journal file. If an error occurs at this point close
    ** and delete the master journal file. All the individual journal files
    ** still have 'null' as the master journal pointer, so they will roll
    ** back independently if a failure occurs.
    
*/
    
for(i=0; i<db->nDb; i++){ 
      Btree 
*pBt = db->aDb[i].pBt;
      
if( i==1 ) continue;   /* Ignore the TEMP database */
      
if( pBt && sqlite3BtreeIsInTrans(pBt) ){
        
char const *zFile = sqlite3BtreeGetJournalname(pBt);
        
if( zFile[0]==0 ) continue;  /* Ignore :memory: databases */
        
if!needSync && !sqlite3BtreeSyncDisabled(pBt) ){
          needSync 
= 1;
        }
        rc 
= sqlite3OsWrite(master, zFile, strlen(zFile)+1);
        
if( rc!=SQLITE_OK ){
          sqlite3OsClose(
&master);
          sqlite3OsDelete(zMaster);
          sqliteFree(zMaster);
          
return rc;
        }
      }
    }


    
/* Sync the master journal file. Before doing this, open the directory
    ** the master journal file is store in so that it gets synced too.
    
*/
    zMainFile 
= sqlite3BtreeGetDirname(db->aDb[0].pBt);
    rc 
= sqlite3OsOpenDirectory(master, zMainFile);
    
if( rc!=SQLITE_OK ||
          (needSync 
&& (rc=sqlite3OsSync(master,0))!=SQLITE_OK) ){
      sqlite3OsClose(
&master);
      sqlite3OsDelete(zMaster);
      sqliteFree(zMaster);
      
return rc;
    }

    
/* Sync all the db files involved in the transaction. The same call
    ** sets the master journal pointer in each individual journal. If
    ** an error occurs here, do not delete the master journal file.
    **
    ** If the error occurs during the first call to sqlite3BtreeSync(),
    ** then there is a chance that the master journal file will be
    ** orphaned. But we cannot delete it, in case the master journal
    ** file name was written into the journal file before the failure
    ** occured.
    
*/
    
for(i=0; i<db->nDb; i++){ 
      Btree 
*pBt = db->aDb[i].pBt;
      
if( pBt && sqlite3BtreeIsInTrans(pBt) ){
        rc 
= sqlite3BtreeSync(pBt, zMaster);
        
if( rc!=SQLITE_OK ){
          sqlite3OsClose(
&master);
          sqliteFree(zMaster);
          
return rc;
        }
      }
    }
    sqlite3OsClose(
&master);

    
/* Delete the master journal file. This commits the transaction. After
    ** doing this the directory is synced again before any individual
    ** transaction files are deleted.
    
*/
    rc 
= sqlite3OsDelete(zMaster);
    assert( rc
==SQLITE_OK );
    sqliteFree(zMaster);
    zMaster 
= 0;
    rc 
= sqlite3OsSyncDirectory(zMainFile);
    
if( rc!=SQLITE_OK ){
      
/* This is not good. The master journal file has been deleted, but
      ** the directory sync failed. There is no completely safe course of
      ** action from here. The individual journals contain the name of the
      ** master journal file, but there is no way of knowing if that
      ** master journal exists now or if it will exist after the operating
      ** system crash that may follow the fsync() failure.
      
*/
      
return rc;
    }

    
/* All files and directories have already been synced, so the following
    ** calls to sqlite3BtreeCommit() are only closing files and deleting
    ** journals. If something goes wrong while this is happening we don't
    ** really care. The integrity of the transaction is already guaranteed,
    ** but some stray 'cold' journals may be lying around. Returning an
    ** error code won't help matters.
    
*/
    
for(i=0; i<db->nDb; i++){ 
      Btree 
*pBt = db->aDb[i].pBt;
      
if( pBt ){
        sqlite3BtreeCommit(pBt);
      }
    }
  }
#endif

  
return rc;
}

分享到:
评论

相关推荐

    SQLite入门与分析

    在SQLite入门与分析(七)---浅谈SQLite的虚拟机.doc中,主要讲解了SQLite如何通过虚拟机执行SQL语句。SQLite的虚拟机,也称为VDBE(Virtual Database Engine),是SQLite的核心组件。它负责解析SQL语句,将其转化为一...

    sqlite-tools-win-x64-3440200.zip

    "sqlite-tools-win-x64-3440200.zip"这个压缩包包含了三个主要的SQLite实用工具,它们分别是sqlite3_analyzer.exe、sqlite3.exe和sqldiff.exe。 1. **sqlite3.exe**:这是SQLite的命令行接口,也是最基础的工具。...

    sqlite-jdbc-3.15.1-API文档-中文版.zip

    赠送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-devel-3.7.17-8.el7.x86_64.rpm

    SQLite入门与分析.doc

    对于那些不需要复杂事务处理和并发控制的场景,SQLite 提供了一个简单、高效的解决方案。然而,对于需要高度并发和复杂事务处理的应用,可能需要考虑使用更为强大的数据库系统,如 MySQL 或 PostgreSQL。

    sqlite-tools-win-x64-3460000.zip

    SQLite3.exe 是 SQLite 数据库引擎的一个命令行接口工具,它允许用户通过文本命令与 SQLite 数据库进行交互。SQLite 是一个开源、轻量级、自包含的 SQL 数据库引擎,广泛应用于嵌入式系统和移动应用中,因为它无需...

    sqlite-shell-linux-x86-3080900.zip

    总的来说,"sqlite-shell-linux-x86-3080900.zip" 是在Linux环境中管理和操作SQLite数据库的重要工具,它提供了一个直接与数据库交互的界面,使得数据管理变得简单易行。无论你是初学者还是经验丰富的开发者,熟悉并...

    SQLite入门分析

    SQLite入门与分析(四)---Page Cache之事务处理.doc SQLite入门与分析(五)---Page Cache之并发控制.doc SQLite入门与分析(六)---再谈SQLite的锁.doc

    sqlite-shell-win32-x86: sqlite3.exe

    --sqllite3 sqlite-shell-win32-x86: sqlite3.exe --svn执行clean up命令时报错“Previous operation has not finished; run 'cleanup' if it was interrupted”。 解决此问题所需文件 2. 为了方便命令行执行,将...

    SQLiteStudio-3.4.4-windows-x64-installer.zip

    SQLiteStudio是一款功能强大的SQLite数据库管理工具,专为Windows操作系统设计。SQLite本身是一个开源、轻量级的嵌入式SQL数据库引擎,广泛应用于各种桌面应用程序、移动设备和服务器环境,尤其适合那些对数据库性能...

    sqlite .net 源码包 sqlite-netFx-source-1.0.86.0

    通过分析这个源码包,开发者可以了解到如何在.NET项目中集成SQLite数据库,包括数据访问层的设计、事务处理、错误处理、查询构建等。同时,源码包还支持多种.NET版本和平台,使得开发者可以根据项目需求选择合适的...

    SQLite-1.0.66.0-setup

    "SQLite-1.0.66.0-setup" 标题表明这是一个关于SQLite数据库的安装程序,版本号为1.0.66.0。这个压缩包可能包含了用于在用户计算机上安装SQLite的必要文件。 描述中的重复信息“SQLite-1.0.66.0-setup”可能是一个...

    sqlite-tools-linux-x86-3350400.zip

    在“sqlite-tools-linux-x86-3350400.zip”这个压缩包中,包含了以下关键的SQLite工具: 1. **sqlite3**: 这是SQLite的主要命令行接口。用户可以通过这个工具执行SQL语句,创建和管理数据库,查询数据,甚至进行...

    sqlite-shell-win32-x86-3080200.zip

    1. **安装与运行**:解压"sqlite-shell-win32-x86-3080200.zip"到任意目录,然后找到sqlite3.exe,双击运行或在命令行中输入其路径启动。 2. **连接数据库**:在命令行中,输入`.open 数据库文件名`,例如`.open ...

    sqlite-shell-linux-x86-3080500.zip

    4. **事务处理**:SQLite3支持ACID(原子性、一致性、隔离性和持久性)事务,确保数据的安全性。 5. **存储方式**:数据存储在单个文件中,便于备份和移动。 6. **性能**:尽管轻量级,但SQLite3在许多场景下表现出...

    wvp-GB28181-pro 适配支持 SQlite3 数据库 支持wvp-GB28181-pro 2.6.9

    wvp-GB28181-pro 适配支持 SQlite3 数据库 支持wvp-GB28181-pro 2.6.9

    PyPI 官网下载 | sqlite3-to-mysql-1.4.5.tar.gz

    在实际使用过程中,`sqlite3-to-mysql`库提供了许多自定义选项,如指定要迁移的特定表、处理数据类型转换、处理主键和外键关系等。这些功能使得用户可以根据具体需求对迁移过程进行精细控制,避免数据丢失或错误。 ...

    sqlite-tools-win32-x86-3290000

    sqlite-tools-win32-x86-3290000 通常包含了用于管理SQLite数据库的命令行工具,如sqlite3.exe,这个工具可以用来创建、打开、查询和管理SQLite数据库文件(通常以.db为扩展名)。 用户可以在该目录下运行sqlite3....

Global site tag (gtag.js) - Google Analytics