传统的数据库必须具备“ACID”,即原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)这四个特性。下面的文章主要阐述SQLite对隔离性的支持,以及对数据库并发控制的影响。
数据库的隔离性决定了多个并发事务同时对数据库进行操作的能力,并且能够防止并发事务交叉执行而导致的数据不一致。
假设一个数据库同时存在两个连接(没有使用SQLite的shared cache机制),A连接只进行读操作,B连接只进行写操作,只有当B连接的事务提交了,A连接才能看到B连接对数据库所做的修改。当B连接没有提交事务的时候,B连接对数据库所做的部分修改对于A连接来说都是不可见的。这是由数据库的隔离性所保证的,不管这两个连接是在同一个线程、同一个进程的不同线程,还是不同进程。
SQLite的隔离性和并发控制是通过事务日志来实现的。目前有两种事务日志模式:回滚模式和WAL(Write Ahead Log)模式。
在回滚模式中,对数据库的修改是直接写到数据库文件中的,在写数据库的过程中由于某些原因比如断电,操作可能被打断,为了保证数据的一致性,被修改的原始数据必须先备份到回滚日志文件中,当出现了数据不一致的情况还能通过回滚日志文件中的数据恢复部分修改的数据到修改前的状态。
在SQLite中回滚日志文件跟数据库文件出于同一个文件夹中,命名规则为数据库文件名加上'-journal'后缀,由于在易信项目中数据库日志模式用的是默认的回滚DELETE模式,该模式会在事务结束后删除掉回滚日志文件,所以基本上都见不到回滚日志文件。
在回滚模式中,SQLite在写数据库时通过给数据库文件加锁,阻止读操作的执行,以此实现隔离性。只有在写事务完成,并且写数据同步到了磁盘后,读操作才被允许执行,这就保证读操作不会读到处于不一致状态下的数据(写操作只执行了一部分)。具体的加锁操作将会在并发控制章节中阐述。
SQLite从3.7.0版本开始引入WAL模式,在该模式中,对数据库的修改并不是直接写到数据库文件中,而是写入到一个单独的WAL文件中。WAL文件中的数据会在适当的时候写回到到数据库文件中。WAL模式的这种特性使得数据库可以同时进行读写事务。
在回滚模式中通过对数据库文件加锁来控制并发,一个数据库文件有可能出于下面这五种加锁状态:
一个数据库连接想要对数据库进行写操作,必须执行以下步骤:
有两个时机可以触发加独占锁的行为,一是写缓存满了需要写回到数据库文件中,二是写事务提交了,从上面的分析可知如果获取独占锁的时候还有共享锁那么操作就会失败,也就是说如果独占锁的获取操作是在事务提交的时候触发的,那么写事务提交是有可能失败的。
从以上可知在数据库文件被独占锁锁住的期间里,数据库的读写是不能并发的。
在WAL模式中,一个读操作的流程如下:
写操作流程:直接将新的数据添加到WAL日志文件中。因为写操作不会影响到读操作,所以读操作和写操作能在同一时刻实行。但需要注意的是一个数据库只有一个WAL日志文件,所以同一时刻只能有一个写操作。
随着写操作的不断执行,WAL日志会越来越大,这时候可以通过执行checkpoint操作将WAL日志的数据写回到数据库文件中,从而调整WAL日志文件的大小。在默认情况下checkpoint操作会自动执行,也可以通过执行“PRAGMA wal_checkpoint”强制执行。
写事务非常快,因为只有一次写操作(回滚模式需要两次写操作,一次是将旧数据写入到回滚日志中,一次是将新数据写入到数据库文件中),并且每次写操作都是顺序的,没有寻址等消耗。
读事务性能会随着WAL日志文件变大而下降,每次读数据都需要检查WAL日志是否有该数据,查找所花的时间是跟日志文件大小成正比的。为了减少WAL日志查找性能损耗,SQLite额外建立了一个WAL索引,“-shm”结尾的文件就是索引文件。官方文档显示在一个基本上都是读操作很少写操作的情况下,性能会降低1%或者2%。
SQLite可以运行在三种线程模式下:
长时间执行一个事务是要避免的:
SQL语句执行之前都要使用sqlite3_prepare系列的函数编译成字节码。该编译的字节码可以被复用,在需要重复执行相同SQL语句的情况下(注意参数是可以不同的),复用该字节码能够节约编译消耗。
sqlite3_exec是对sqlite3_prepare,sqlite3_step,sqlite3_finalize这一流程的封装,SQL只执行几次的情况下可以直接调用。
在FMDatabase中如果想要复用字节码需要执行setShouldCacheStatements:显示启用。
可以将多个SQL语句放在“BEGIN”,"COMMIT"中执行,减少自动事务提交带来的消耗。默认情况下每次SQL语句的执行都是一个事务操作,事务操作包括数据库加锁,数据持久化等一些列操作,“BEGIN”其实就是关闭了自动事务,减少加锁、持久化这些行为执行次数,已达到提高性能的目的。
本文来自网易实践者社区,经作者徐晖权发布。