SQLite3 提供了一个新的锁和同步机制来提高并发,减少死锁。
SQLite3的锁和同步有Pager Module(pager.c)负责处理。Pager Modue负责SQLite事务的ACID, 也提供缓存功能。Pager Modue不需要知道BTree, 字符编码, 索引的结构, Pager Module用来管理Page, 一个Page对应一个DiskBlock, 大小一般是1024Byte。
1. SQLite3 数据库的锁状态
UNLOCKED
SHARED 共享锁,可以读,不能写
RESERVED 保留锁, 表示数据库将被写,但写在缓存中,还不能写入数据库,因为有其他链接持有共享锁,还在读数据。 一个数据库只能有一个保留锁, 保留锁可以和共享锁共存, 与PENDING锁的不同之处在于还能获得新的共享锁,PENDING锁被激活时, 不能再获得共享锁。
PENDING(未决)此时,其他链接不再能获取共享锁,即,等待读的链接退出,不接受新的链接来读
EXCLUSIVE(排他),读的链接都退出了,可以写入数据库了。
sqlite 锁状态很容易理解: 多个链接可以同时从一个paper中读数据,但只允许一个链接往paper写数据,而且在写数据的时候不允许有其他链接读数据,防止造成数据不一致。另外,链接持有共享锁时,可以从paper中读数据,持有保留锁时可以往缓存中写数据和修改,如果想要把数据写入paper中,就要限制新的链接获取共享锁、等待有共享锁的链接执行完毕,此时称为未决。
2. 回滚日志文件
如果有更新数据库操作, SQLite就会生成回滚日志文件, 以"-journal"的文件名结尾, 与数据库文件存放在同一目录下。 如果多个数据库同时工作, 每个数据库都有自己的回滚日志文件, 并且还有一个master journal日志文件。master journal没有数据, 只包含各个回滚日志文件名。每个数据库的回滚日志文件也会包含master journal文件名。
当访问数据库时发现有"hot journal"时, SQLite就会进行回滚工作, 回滚结束就删除回滚日志文件。
处理"hot journal"
(1) 尝试获得SHARED LOCK, 如果失败, 立即结束, 返回SQLITE_BUSY
(2) 检查是否有"hot journal", 如果没有立即返回, 否则继续执行以下步骤
(3) 尝试获得PENDING LOCK, 然后EXCLUSIVE LOCK, 如果失败, 表示其他进程正在做回滚, 释放所有锁, 关闭数据库, 返回SQLITE_BUSY。 否则继续执行
(4) 读回滚日志文件, 回滚数据库文件
(5) 删除回滚日志文件
(6) 删除master journal 文件
(7) 释放PENDING LOCK和EXCLUSIVE LOCK, 但是保留SHARED LOCK
3. 写数据库文件步骤
(1) 获得共享锁
(2) 获得RESERVED LOCK, 如果失败, 返回SQLITE_BUSY, 否则继续执行
(3) 生成回滚日志文件, 写入磁盘, 等待写完成继续执行
如果是单个数据库文件
(4) 请求获得PENDING LOCK
(5) 请求获得EXCLUSIVE LOCK
(6) flush/fsync, 将更新写入磁盘
(7) 删除回滚日志文件
(8) 释放EXCLUSIVE LOCK, PENDING LOCK, RESERVED LOCK, 获得SHARED LOCK
如果是多个数据库文件事务
(4) 请求获得PENDING LOCK 和EXCLUSIVE LOCK, 确保所有数据库都获得EXCLUSIVE LOCK
(5) 生成master journal文件和每个数据库的回滚日志文件
(6) flush/fsync, 将更新写入磁盘
(7) 先删除master journal 文件, 再删除所有的回滚日志文件
(8) 释放所有数据库上的EXCLUSIVE LOCK, PENDING LOCK
4. SQL事务
默认SQLite autocommit=true
BEGIN TRANSACTION - COMMIT 命令使得SQLite不在autocommit下工作。当SQLite执行BEGIN命令时, 不会获得任何锁, 直到执行到第一个SELECT, 才获得一个SHARED LOCK, 执行到UPDATE/INSERT/DELETE才获得REVERSED LOCK, 当缓存满或者COMMIT时才请求获得EXCLUSIVE LOCK。
COMMIT并非真正的将更新写到磁盘, COMMIT使得SQLITE回到autocommit=true 模式, autocommit会负责将更新写到磁盘。
5 写同步
初用sqlite3插入数据时,插入每条数据大概需要100ms左右。如果是批量导入,可以引进事物提高速度。但是假设你的业务是每间隔几秒插入几条数据,显然100ms是不能容许的。解决办法是,在调用sqlite3_open函数后添加下面一行代码:
sqlite3_exec(db, "PRAGMA synchronous = OFF; ", 0,0,0);
上面的解决办法貌似治标不治本,为什么加上上面的代码行,速度会提高那么多?网上解释如下:
磁盘同步
1.如何设置:
PRAGMA synchronous = FULL; (2)
PRAGMA synchronous = NORMAL; (1)
PRAGMA synchronous = OFF; (0)
2.参数含义:
当synchronous设置为FULL (2), SQLite数据库引擎在紧急时刻会暂停以确定数据已经写入磁盘。这使系统崩溃或电源出问题时能确保数据库在重起后不会损坏。FULL synchronous很安全但很慢。
当synchronous设置为NORMAL, SQLite数据库引擎在大部分紧急时刻会暂停,但不像FULL模式下那么频繁。 NORMAL模式下有很小的几率(但不是不存在)发生电源故障导致数据库损坏的情况。但实际上,在这种情况 下很可能你的硬盘已经不能使用,或者发生了其他的不可恢复的硬件错误。
设置为synchronous OFF (0)时,SQLite在传递数据给系统以后直接继续而不暂停。若运行SQLite的应用程序崩溃, 数据不会损伤,但在系统崩溃或写入数据时意外断电的情况下数据库可能会损坏。另一方面,在synchronous OFF时 一些操作可能会快50倍甚至更多。在SQLite 2中,缺省值为NORMAL.而在3中修改为FULL。
3.建议:
如果有定期备份的机制,而且少量数据丢失可接受,用OFF。
注意上面红色加粗的字样。总结:如果你的数据对安全性完整性等要求不是太高,可以采用设置为0的方法,毕竟只是“数据库可能会损坏”,至于损坏几率为多大,笔者也暂不知晓。。。。。。还没遇到过损坏,不知什么时候才会发生。