SQLite数据库事务处理详解教程

说到事务一定会提到ACID,所谓事务的原子性,一致性,隔离性和持久性。对于一个数据库而言,通常通过并发控制和故障恢复手段来保证事务在正常和异常情况下的ACID特性。sqlite也不例外,虽然简单,依然有自己的并发控制和故障恢复机制。Sqlite学习笔记(五)&&SQLite封锁机制
已经讲了一些锁机制的原理,本文将会详细介绍一个事务从开始,到执行,最后到提交所经历的过程,其中会穿插讲一些sqlite中锁管理,缓存管理和日志管理的机制,同时会介绍在异常情况下(软硬件故障,比如程序异常crash,主机掉电等),sqlite如何将数据库恢复到事务之前的状态。本文大量参考了
sqlite的官方文档,结合自己的理解,希望能把这个过程说清楚。

1.事务提交

1.1 开启一个事务

在向数据库文件写数据前,sqlite首先需要访问sqlite_master表获取元数据信息,用来对SQL语句进行语义分析,判断语句的合法性。从数据库读数据第一步,是对数据库文件上一个Shared
Lock。Shared Lock允许多个事务同时读一个数据库文件,但是Shared Lock会阻止写事务向数据库文件写入数据。


1.2 读数据

获取Shared
Lock后,我们可以从数据库文件中读取数据了。我们假设缓存中没有我们的page,因此需要通过读文件读取我们需要的page。这里说明下,sqlite的数据库文件实质是有一个个大小相同的page组成,默认情况下,一个page大小为1024B。通常情况下,我们需要读取若干个
page,并把这些page缓存在应用本地的cache中,这样下次访问就不需要再次从文件中读取。这里我们假设需要读取3个page,用绿色块表示。


1.3 获取Reserved Lock

在向数据库写数据之前,Sqlite需要获取一个Reserved Lock,Reserved Lock与Shared
Lock类似,同时允许其他事务读取数据库。Reserved Lock与Shared Lock兼容,但与Reserved
Lock互斥,即同一个时刻只允许有一个Reserved Lock。持有Reserved
Lock表示事务准备要修改数据文件了,由于还没有真正修改文件,因此允许其他事务继续进行读操作,但不允许其他事务进行写数据库操作。


1.4 创建日志文件

在sqlite中,有两种日志技术,影子分页技术和WAL(write ahead
log)技术。影子分页技术是sqlite默认采用的方式,后面的讨论都是基于这种假设。在操作数据文件之前,sqlite首先创建一个日志文件,并将准备要修改的page的内容写入日志,通过这种方式保留了恢复事务的所有原始信息。无论是数据库文件,还是日志文件,最基本的操作单位都是page。


1.5 修改数据

前面提到,sqlite修改数据前,先将page读到cache中,因此修改会首先修改cache中的数据。由于每个连接都有自己独立的page
cache,因此写事务修改自己page
cache中的数据,不会影响其他事务,其他事务依然会读到原始的page数据,不会导致脏读。下图中红色表示修改块,从图中可以看到,只有用户自身
cache的page变成了红色。

1.6 刷日志文件

修改完成后,首先将日志文件写入磁盘。这个过程非常重要,只有通过刷盘操作(fsync)将日志持久化,才能在掉电的情况下,通过日志恢复数据页。同时,这个动作也非常耗时。


1.7 获取Exclusive Lock

现在我们需要将之前对page cache的修改写入数据库文件,达到持久化目的。在这个动作之前,首先需要持有Exclusive
Lock,获取该锁实际包含两个步骤,首先持有一个Pending Lock,然后再持有Exclusive Lock。Pending
Lock允许持有读锁事务继续进行读操作,但不允许新的读事务进来。由于新的读事务被阻止,则将读事务数目限制在一定的范围,而已有的读事务迟早都会执行完,写事务最终可以获取到Exclusive
Lock,通过这种方式避免写事务饿死的情况。

1.8 将修改写入数据文件并刷盘

一旦持有了Exclusive
Lock,则此时sqlite中只有一个事务,没有其他读事务去读文件。因此,这时候向数据文件中写数据是安全的。为了保证写入动作真正落入磁盘,还需要进行刷盘动作。与刷日志一样,将数据文件修改刷盘动作也是为了保证掉电情况下,更新依然可以持久化,同样这个操作也很耗时。其中红色块表示修改块,此时用户进程空间,OS
buffer,以及DISK都已经修改。


1.9 删除日志文件

进行这步时,日志文件和数据文件修改都已经固化到磁盘。如果在1.8步之前,发生掉电,由于日志文件已经安全落盘,因此可以将数据库恢复到事务开始前的状态。由于数据文件修改已经固化,我们可以将日志文件删除。通过日志文件的存在与否,判断我们是需要将事务回滚还是提交。由于删文件也是一个比较耗时的动作,sqlite对此进行了优化,通过参数选项,可以选择将日志全部初始化0,或是直接将文件截断,达到提高性能的目的。


1.10 释放Exclusive Lock

最后一步是释放Exclusive Lock,这样其他事务才有机会读、写数据文件。这里有一个问题,每个连接都有自己的page cache,如果page
cache中的内容已经被改了,并写入到了文件中,那么其它事务如何感知,将自己本地的old-page清理,重新从文件中读?sqlite通过一个计数器来控制,这个计数器存在数据库文件的第一个page中。每次数据文件修改时,这个值也会同时自增。事务开始时,会读取计数器,在读取page
时,会再次检查计数器是否发生变化,如果发生变化,说明有事务提交,则将本地的cache全部清空,重新从数据库文件中获取。


2.事务回滚

正常情况下,通过上述的事务提交流程,就可以保证事务的ACID特性。但是事务在执行过程中发生异常呢,这时候就需要通过事务回滚来将数据库恢复到事务开始前的状态。下面假设一种情况,来介绍回滚流程。

2.1 发生故障

假设在1.8之前,写数据库文件时,发生了掉电故障。当故障恢复后,情形可能如右图所示,只有部分页写入了磁盘,甚至有一个页可能只写入了一部分。由于执行到这个步骤时,日志已经安全落盘,因此可以借助日志进行恢复。


2.2 热日志

任何一个连接在操作数据库之前,会首先判断是否有热日志存在,因为有热日志存在意味着可能需要故障恢复。所谓热日志,是指需要事务提交过程中发生了故障,需要利用日志恢复。


2.3 回滚未完成的操作

在利用日志进行恢复前,首先持有Exclusive Lock,这样避免多个连接同时进行故障恢复,持有Exclusive
Lock后,才可以开始修改数据库文件。sqlite从日志文件中读取原始的数据页,然后将数据页写回到数据文件中。由于日志文件头部记录了事务开始时数据文件的大小,sqlite利用这个信息来讲数据文件进行截断到原来的大小,保持文件大小恢复到事务开始时的水平。


2.4 删除日志文件

当所有日志文件中的数据页都已经拷贝到数据文件中后,进行一次刷盘操作,确保修改持久化,这时候日志文件可以被删除了。恢复完成后,将Exclusive Lock
降级到Shared
Lock。这个过程完成后,数据库完成恢复。由于整个过程都是sqlite自动完成,用户完全无感知。对于用户而言,任何时候使用sqlite操作数据文件都是安全的,即使在发生了异常的情况下。



sqlite处理事务的一个例子
 
事务在数据库中是一个重要的概念,使用事务可以保证数据的统一和完整性。同时也可以提高效率。拿我们上面创建的persons表来说,假设我要一次插入20个人的名字才算是操作成功,那么,在不使用事务的情况下,如果插入过程中出现异常或者在插入过程中出现一些其他数据库操作的话,就很有可能影响了操作的完整性。所以事务可以很好地解决这样的情况,首先事务是可以把启动事务过程中的所有操作视为事务的过程。等到所有过程执行完毕后,我们可以根据操作是否成功来决定事务是否进行提交或者回滚。提交事务后会一次性把所有数据提交到数据库,如果回滚了事务就会放弃这次的操作,而对原来表的数据不进行更改。

那么,如何启动,提交还有回滚事务呢?SQLite中分别是:BEGIN、COMMIT和ROLLBACK。下面来看一下例子:

 @try{

char *errorMsg;

if (sqlite3_exec(_database, "BEGIN", NULL, NULL, &errorMsg)==SQLITE_OK) {

NSLog(@”启动事务成功”);

sqlite3_free(errorMsg);

       sqlite3_stmt *statement;

if (sqlite3_prepare_v2(_database, [@"insert into persons(name) values(?);" UTF8String], -1, &statement, NULL)==SQLITE_OK) {

//绑定参数

const char *text=[@”张三” cStringUsingEncoding:NSUTF8StringEncoding];

sqlite3_bind_text(statement, index, text, strlen(text), SQLITE_STATIC);

if (sqlite3_step(statement)!=SQLITE_DONE) {

sqlite3_finalize(statement);

}

}

if (sqlite3_exec(_database, "COMMIT", NULL, NULL, &errorMsg)==SQLITE_OK) {

NSLog(@”提交事务成功”);

}

sqlite3_free(errorMsg);

}else{

sqlite3_free(errorMsg);

}

}

@catch(NSException *e){

char *errorMsg;

if (sqlite3_exec(_database, "ROLLBACK", NULL, NULL, &errorMsg)==SQLITE_OK) {

NSLog(@”回滚事务成功”);

}

sqlite3_free(errorMsg);

}

@finally{

}

时间: 2024-08-04 11:38:17

SQLite数据库事务处理详解教程的相关文章

Android编程之SQLite数据库操作方法详解

本文实例讲述了Android SQLite数据库操作方法.分享给大家供大家参考,具体如下: SQLite and Android SQLite简介 SQLite是一个非常流行的嵌入式数据库,它支持SQL语言,并且只利用很少的内存就有很好的性能.此外,它还是开源的,任何人都可以使用它. SQLite由以下几个组件组成:SQL编译器.内核.后端以及附件.SQLite通过利用虚拟机和虚拟数据库引擎(VDBE),使调试.修改和扩展SQLite的内核变得更加方便. SQLite支持的数据类型包括: 1.

iOS中SQLite数据库使用详解

使用SQLite数据库 创建数据库 创建数据库过程需要3个步骤: 1.使用sqlite3_open函数打开数据库: 2.使用sqlite3_exec函数执行Create Table语句,创建数据库表: 3.使用sqlite3_close函数释放资源. 这个过程中使用了3个SQLite3函数,它们都是纯C语言函数,通过Objective-C去调用C函数当然不是什么问题,但是也要注意Objective-C数据类型与C数据类型兼容性问题. 下面我们使用SQLite技术实现备忘录案例,与属性列表文件实现

Android编程操作嵌入式关系型SQLite数据库实例详解_Android

本文实例分析了Android编程操作嵌入式关系型SQLite数据库的方法.分享给大家供大家参考,具体如下: SQLite特点 1.Android平台中嵌入了一个关系型数据库SQLite,和其他数据库不同的是SQLite存储数据时不区分类型 例如一个字段声明为Integer类型,我们也可以将一个字符串存入,一个字段声明为布尔型,我们也可以存入浮点数. 除非是主键被定义为Integer,这时只能存储64位整数 2.创建数据库的表时可以不指定数据类型,例如: 复制代码 代码如下: CREATE TAB

SQLite数据库约束详解

一.约束 Constraints 在SQLite数据库中存储数据的时候,有一些数据有明显的约束条件. 比如一所学校关于教师的数据表,其中的字段列可能有如下约束: 年龄 - 至少大于20岁.如果你想录入一个小于20岁的教师,系统会报错. 国籍 - 默认中国.所谓默认,就是如果你不填写,系统自动填上默认值. 姓名 - 不能为空.每个人都有名字嘛. 员工号 - 唯一.这个可不能乱,工资发错了就麻烦了. 上面提到的大于.默认.不能为空.唯一等等,就是数据的约束条件. 我们在用CREATE TABLE 创

iOS数据持久化-SQLite数据库使用详解

使用SQLite数据库 创建数据库 创建数据库过程需要3个步骤: 1.使用sqlite3_open函数打开数据库: 2.使用sqlite3_exec函数执行Create Table语句,创建数据库表: 3.使用sqlite3_close函数释放资源. 这个过程中使用了3个SQLite3函数,它们都是纯C语言函数,通过Objective-C去调用C函数当然不是什么问题,但是也要注意Objective-C数据类型与C数据类型兼容性问题. 下面我们使用SQLite技术实现备忘录案例,与属性列表文件实现

Android编程操作嵌入式关系型SQLite数据库实例详解

本文实例分析了Android编程操作嵌入式关系型SQLite数据库的方法.分享给大家供大家参考,具体如下: SQLite特点 1.Android平台中嵌入了一个关系型数据库SQLite,和其他数据库不同的是SQLite存储数据时不区分类型 例如一个字段声明为Integer类型,我们也可以将一个字符串存入,一个字段声明为布尔型,我们也可以存入浮点数. 除非是主键被定义为Integer,这时只能存储64位整数 2.创建数据库的表时可以不指定数据类型,例如: 复制代码 代码如下:CREATE TABL

基于PHP+jQuery注册模块开发详解教程

/* ******* 环境: Apache2.2.8 + PHP5.2.6 + MySQL5.0.51b + jQuery-1.8.3.min.js ******* ******* 其他组件:Zend_mail( Zend_framework 1.11.11 ) ******* Date:2014-09-25 ******* Author:小dee */ 了一个简单的PHP+jQuery注册模块,需要填写的栏目包括用户名.邮箱.密码.重复密码和验证码,其中每个栏目需要具备的功能和要求如下图: 开

有关jsp在windows下的配置及连接SQLServer数据库的详解

js|server|sqlserver|window|数据|数据库|详解 最近在学习jsp,有关jsp的运行环境的配置问题着实让我费了不少功夫,环境配置好了,连接SQL Sever 2000数据库时又出了不少问题,鉴于此我把自己配置这两方面的详细步骤给大家共享一下,希望刚刚接触jsp的并立志在jsp方面有所成就的同仁少走一些弯路,让我共同进步.有希望与我交流的请加我qq:26544472(在验证信息中请注明jsp交流) (一)有关jsp在windows下的配置: Sun推出的JSP(Java S

如何远程调用Access数据库方法详解

  如何远程调用Access数据库方法详解: 使用了TCP/IP,ADO及XML(需要安装Microsoft XML 4.0.).分服务器和客户端两部分,服务器可以多用户同时连接.远程连接Access数据库有很多方法,我以前已经比较详细的回答过(见下面所列的5种方法),我现在这个例子属于其中的第3种方法(不需要使用RDS或Web服务器). 远程连接access数据库的几个方法: 1.建立VPN(Virtual Private Network),这样你的电脑和主机的连接就与局域网无异,然后把服务器