SQLite 知识摘要 --- 事务

本篇主要从SQLite事务执行的原理中寻找如何更高效地使用它。

本篇预备知识

我们先来了解下SQLite执行事务的基本流程,状态变化过程,再分析怎么使用才更优。SQLite定义的锁的状态有如下几种:

  • UNLOCK:最初始状态,没有任何锁在数据库上;
  • SHARED:共享状态,允许读取数据,但是不能写入和修改,同一时间允许有多个SHARED存在, 共享锁只是针对操作系统的磁盘缓存;
  • RESERVED:这个锁意味着进程将要对数据库进行写操作。某一时刻只能有一个RESERVED锁,但是RESERVED锁和SHARED锁可以共存,而且可以对数据库加新的SHARED锁。引入这个状态是为了提高并发性,在这个状态下可以先修改缓存数据,直到将修改写入磁盘的时候再加上排他锁;
  • EXCLUSIVE:真正将数据写入磁盘的过程,此时不允许其他任何写入读取操作,是排他锁;
  • PENDING:可以理解为一个中间状态,从限制小的状态往限制高的状态变化的一个过程。例如从RESERVED向EXCLUSIVE转变的时候需要经过这个状态,需要等待已有的读写连接完成之后再进入EXCLUSIVE。

事务在执行过程中锁状态之间变化,如下图所示:

Screen Shot 2017-03-28 at 10.55.14 PM.png

** SQLite一次事务过程
**

一次事务.png

**整个详细的流程看 **👉 这里

一般来说,Reserved Lock 和日志文件是一一对应的。如果当pager 首次打开数据库,会做一次完整性检查。如果发现有日志文件但是没有Reserved Lock ,数据库会进入恢复模式。


Recovery.png

进入恢复模式后,会直接从shared 状态到pending 状态。那么在数据库连接成功恢复数据库以前不会有其他操作。

按照正常的事务,一次操作大概要经历:

  • 一次文件创建(回滚日志)
  • 两次写入 (修改前把原始数据写入回滚日志/数据页的修改---数据页在系统缓存)
  • 两次flush 文件 (回滚日志和数据库变更冲入本地磁盘)
  • 一次回滚日志删除
  • 三次加锁

我们可以根据实际的使用场景来进行优化。

优化点:

  • 需要批量更新数据,可以显式使用事务
  • 磁盘同步方式
  • 设置高效的日志模式
  • 使用事务来避免死锁

显式事务

一个新事务的创建和关闭,消耗是非常大的,因为它需要打开、修改和关闭日志文件。在默认情况下,调用Sqlite statement执行一条SQL语句,就会创建一个事务,在执行完这条语句后自动关闭事务。那如果我们连续执行很多条SQL的话,会不断创建和关闭事务,这是非常浪费的,对性能的影响是非常大。对于这种情况,我们可以使用BEGIN TRANSACTION和END TRANSACTION来自助选择事务创建和提交的时机,例如:

    sqlite_exec(sqlitedb, "BEGIN TRANSACTION;",...);
    ...
    执行N条SQL
    ...
    sqlite_exec(sqlitedb, "END TRANSACTION;",...);

加上BEGIN END之后,N条SQL只创建了一个事务,不加的话会开N个事务来完成,效果可想而知。

磁盘同步方式

SQLite在将数据提交给系统(OSBuffers)后由系统写入磁盘,但是在这个过程中系统有可能会出现掉电或者写入失败等异常情况,如果SQLite不等待系统执行结果,可能会误认为操作已成功,但实际上数据已经不一致了。对于这种情况,SQLite提供了3种同步方式:

PRAGMA synchronous = (0 | OFF) | (1 | NORMAL) | (2 | FULL)

在FULL模式下, SQLite数据库引擎总是会暂停以确定数据已经写入磁盘,这种模式可以保证系统崩溃或者掉电后重启数据不会收到损坏,很安全但很慢。在NORMAL模式下,SQLite数据库引擎在大部分情况下会暂停,但不像FULL模式下那么频繁,这种方式比FULL模式快,但是存在极小概率在系统掉电或故障时数据库遭到损坏。在OFF模式下,SQLite将数据提交给系统之后不会等待结果,直接继续执行,也就是说在一次事务的过程中会少了两次Flush文件操作。这种模式下如果系统在写入的时候崩溃或者异常,数据库就可能会被破坏,但这种模式下有些操作可以比FULL下快一个数量级。

Synchronous_off.png

默认情况是NORMAL,如果对安全性要求极高的话,可以选择FULL模式,如果非常追求效率又不介意数据库损坏的话(例如定期做数据库自动备份,损坏了仍可还原),可以选择OFF。

设置高效的日志模式

日志文件是SQLite实现回滚至关重要的东西。默认情况下,SQLite在将修改写入磁盘之前,会先将修改日志刷入磁盘再将修改页面写入磁盘,写入完成之后再将日志清理掉。如果在写入的过程中Crash,SQLite能在下次启动时根据日志文件恢复。但这会增加额外的磁盘读写开销,影响整体的事务执行时间。不过Sqlite提供了多种日志模式,可以通过如下命令设置:

PRAGMA journal_mode = DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF

DELETE是默认方式,就是在事务执行后将日志文件删除;TRUNCATE方式则是不删除文件,直接将文件内容清空(在很多系统上,这种清空比删除文件要快);PERSIST方式也不会删除文件,而是将文件头中长度字段置为0,在某些平台上这种方式会优于前两者;MEMORY方式则直接将日志放在内存,不用磁盘存储,这样速度会很快但是如果宕机,日志也会丢失,数据可能被破坏无法恢复;WAL(Write-Ahead Logging)是Sqlite3.7以后才有的一种模式,这种模式的原理是修改并不直接写入到数据库文件中,而是写入到另外一个称为WAL的文件中,在随后的某个时间才被写回到数据库文件中,这种方式可以提高事务的并发性,但是一旦进入这种模式就无法更改页面大小,不能以只读方式打开数据库,且访问数据库的所有程序必须在同一主机上并支持共享内存技术,对于读取多写入少的场景反而会更慢,像微信这种读写频繁的app,很适合用WAL;OFF则是完全禁用回滚日志的功能。

一般情况下,可选择TRUNCATE或PERSIST模式,会有性能上的帮助;对于那些实时性要求非常高但是数据一致性要求不是很高的场景,可以选择MEMORY模式;3.7以上的版本,如果修改的数据量不是特别大或者不是读取多写入少的场景,可以考虑WAL模式。

使用事务来避免死锁

事务创建的时候会对数据库文件加锁,所以在多线程情况下需要注意及时结束事务,否则会影响到其他操作。尽管SQLite有预防死锁的机制(原理是在获取锁的时候重试有限次,超过就返回SQLITE_BUSY错误),避免程序死掉,但还是会出现下面这种并非我们想看到的现象:

deadLock.png

最终Session A和Session B都失败了,这个问题可以通过选择合适的事务类型来避免。

3种事务类型

  • DEFERRED
  • IMMEDIATE
  • EXCLUSIVE
    我们可以通过下面的BEGIN命令来指定:
BEGIN [ DEFERRED | IMMEDIATE | EXCLUSIVE ] TRANSACTION

DEFERRED是默认类型,事务起始不会获取任何锁,从UNLOCKED状态开始,直到事务需要对数据库进行读或者写的时候才会获取对应的锁;IMMEDIATE起始就尝试获取RESERVED锁,保证没有别的连接可以写数据库,但是别的连接可以对数据库进行读操作,也就是说它会阻止其它的连接BEGIN IMMEDIATE或者BEGIN EXCLUSIVE;而EXCLUSIVE事务会试着获取对数据库的EXCLUSIVE锁,一旦成功,EXCLUSIVE事务保证没有其它任何连接,所以就可对数据库进行读写操作了。

例子中,Session A和Session B都需要写数据库,如果两者创建的时候都选择的是IMMEDIATE事务,那这种失败的情况就不会发生了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容