事务定义了一组SQL命令的边界,这组命令或者作为一个整体被全部执行,或者都不执行,这称为数据库完整性的原子性原则.
事务的范围
事务由3个命令控制:begin,commit和rollback.begin开始一个事务,begin之后的所有操作都可以取消,如果连接终止前没有发出commit,也会被取消.commit提交事务开始所后执行的所有操作,rollback还原begin之后所有的操作.
sqlite> begin;
sqlite> delete from test;
sqlite> rollback;
sqlite> select count(*) from test;
比如上面开始了一个事务,先删除了test表中所有的行,但是有用rollback进行了回滚.在执行select时.你会发现表中没有发生任何改变.
默认情况下,SQLite中每条SQL语句自成事务(自动提交模式).也就是说,如果没有手动使用begin...commit/rollback定义事务的范围,SQLite默认每条单独的SQL命令就是begin....commit/rollback的事务.这种情况下,所有成功完成的命令都自动提交.同样.所有遇见错误的命令都回滚,
冲突的解决
违反约束会导致事务的终止.SQLite有其独特的方法指定不同的方式来处理约束违反,这种功能称为冲突解决.SQLite提供五种可能的冲突解决方案:
replace:当违反了唯一性约束时,SQLite将造成这种违反的记录删除,以插入货修改的新纪录代替,SQL继续执行,且不报错.
ignore:当违反约束时,SQLite允许命令继续执行,违反约束的行保持不变.,它之前之后的记录继续修改,且不报错.
fail:当违反约束时,SQLite终止命令,但是不恢复约束违反之前已经修改的记录.在约束违法发生前的改变将保留.
abort(SQLite默认的冲突解决方法):当违反约束时,SQLite恢复命令所做的所有改变并终止命令.abort是最昂贵的冲突解决方案,要求额外的工作.
rollback:当违反约束时,SQLite执行回滚.终止当前命令和整个事务.
冲突解决方法可以在SQL明命令中指定,也可以在表和索引的定义中执行,冲突解决策略紧跟在insert或者update后面,.并加上前缀or,例如:
update or fail test set id=100 modified='yes';
insert or ignore into from test values('aaron');
也可以在表内定义时,为单个字段指定冲突解决方法,例如:
create temp table cast(name text unique on conflict rollback);
数据库锁
在Sqlite中,锁和事务是紧密联系的.SQLite采用粗粒度的锁.当一个连接要写数据库时,所有其他的连接被锁住,知道写连接结束它的事务.SQLite使用锁逐步提升机制,为了写数据库,连接需要逐级获得排他锁.SQLite有5种不同的锁状态:未加锁(unlocked),共享(shared),预留(reserved),未决(pending)和排他(exclusive).每个数据库连接在同一时刻只能处于其中一个状态,每种状态(未加锁状态除外)都有一种锁与之对应.SQlite锁转换图如下图所示:
最初的状态是未加锁状态,在此状态下,连接还没有访问数据库.当连接一个数据库,甚至已经用begin开始了一个事务时,连接都还处于未加锁状态.
未加锁状态的下一个状态就是共享状态.为了能够从数据库中读(而不是写)数据,连接必须首先进入共享状态,也就是说,首先要获得一个共享锁.多个连接可以同时获得并保持共享锁,也就是说,多个连接可以同时从一个数据库中读取数据,但是哪怕只有一个共享锁还没有释放,也不允许任何连接写数据库.
如果一个连接想要写数据库,他必须首先获得一个预留锁.一个数据库同时只能有一个预留锁,该预留锁可以与共享锁共存,他是写数据库的第一阶段,预留锁既不阻止其他拥有共享锁的连接继续读数据可,也不阻止其他连接获得新的共享锁.
一旦一个连接获得了预留锁,他就可以开始处理数据库修改的操作了,尽管这些修改只能在缓冲区中进行,而不是实际写到磁盘,对读出内容所做的修改保存在内存缓冲区.当连接想要提交修改(或事务)时,为了得到排他锁,必须首先将预留锁提升为未决锁.获得未决锁之后,其他连接就不能在获得新的共享锁了,但已经拥有共享所的连接仍然可以继续正常读取数据库,此时,拥有未决锁的连接等待其他拥有共享锁的连接完成工作并释放共享锁.
一旦所有的其他的共享锁都被释放,拥有未决锁的连接就可以将其锁提升为排他锁,此时就可以自由的对数据库进行修改,所有以前所缓存的修改都会被写到数据库文件中.
事务类型
SQLite有三种不同的事务类型,他们以不同的锁状态启动事务.事务可以开始于:deferred,immediate和exclusive.事务类型在begin命令中指定:
begin [deferred | immediate | exclusive ] transaction
一个deferred直到必须使用时才获取锁.因此,对于延迟事务,begin语句本身不会做什么事情,它从未锁定状态开始.这是默然的情况.
由begin开始的immediate事务在begin执行时试图获取预留锁.若果成功,begin immediate 保证没有其他的连接可以写数据库,其他的连接可以继续对数据可进行读操作.预留锁的其他结果是没有其他连接能成功启动begin immedicate 或者begin exclusive 命令,当其他连接执行上述命令时,SQLite会返回SQLITE_BUSY错误.这时您可以对数据库进行修改操作,但是调用commit时,如果其他的读事务还没有完成,会返回SQLITE_BUSY错误,需要等他们执行完毕才能提交事务.
exclusive事务会试着获取对数据库的排他锁,一旦成功,exclusive事务保证数据库中没有其他的活动连接,所以可对数据库进行任意的读写操作.
基本的准则是:如果使用的数据库没有其他连接,用begin就足够了,但是,如果使用的数据库有其他也会对数据库进行写操作的连接,就得使用begin immedicate 或者begin exclusive 开始事务.