SQLite 文件锁和写同步


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的方法,毕竟只是“数据库可能会损坏”,至于损坏几率为多大,笔者也暂不知晓。。。。。。还没遇到过损坏,不知什么时候才会发生。

展开阅读全文

没有更多推荐了,返回首页