原文:https://www.sqlite.org/wal.html

预览

SQLite实现原子提交和回滚的默认方法是回滚日志。从3.7.0版本(2010-07-21)开始,可以使用一个新的“写前日志”选项(以下称为“WAL”)。

wal模式优点

  1. 大部分情况下,wal模式要比其他模式快很多;
  2. wal提供了更多的并发性,读时不会阻塞写,写时不会阻塞读;
  3. 使用wal使得磁盘IO操作更加连续;
  4. wal使用的fsync()操作要少得多,因此在fsync()系统调用中断的系统上,不太容易出现问题;

wal模式缺点

  1. WAL通常要求VFS支持共享内存原语。 (例外:没有共享内存的WAL)内置的unix和Windows VFS支持此功能,但自定义操作系统的第三方扩展VFS可能不支持。
  2. 使用数据库的所有进程必须位于同一台主机上; WAL无法在网络文件系统上运行。
  3. 涉及多个数据库(附加数据库)的事务中,对每个单独的数据库操作是原子性的,但是对于整个集合来说不是原子性的;
  4. 进入WAL模式后,无法在空数据库上或使用VACUUM或使用备份API从备份恢复时更改page_size。 必须处于回滚日志模式才能更改页面大小。
  5. 注意数据库文件的权限,一些情况下会导致无法打开数据库。每个数据库都有一个附加的准持久性“-wal”文件和“-shm”共享内存文件,这2个文件在第一个连接和最后一个连接生命期内存在。假设存在数据库/var/log/wyq.db,如果存在/var/log/wyq-shm文件,则打开数据库的进程必须要有wyq-shm文件的写权限,否则无法打开;如果不存在/var/log/wyq-shm文件,则打开数据库的进程必须要有/var/log目录的写权限,否则无法打开数据库;不过,从版本3.22.0(2018-01-22)开始,如果-shm和-wal文件已存在或者可以创建这些文件或数据库是不可变的,则可以打开。
  6. 在大多数读取和很少写入的应用程序中,WAL可能比传统的rollback-journal方法稍慢(可能慢1%或2%)。所以wal模式适合运行在写多读少的应用中。
  7. WAL适用于较小的交易。 WAL对于非常大的事务不适用。 对于大于约100M字节的事务,传统的回滚日志模式可能会更快。 对于超过1G字节的事务,WAL模式可能会因I / O或磁盘已满错误而失败。 从版本3.11.0(2016-02-15)开始,WAL模式与大型事务一样有效,与回滚模式一样。

wal模式的工作方式

传统的日志回滚模式是这样工作的:首先拷贝一份原始的数据库内容到一个单独的回滚日志文件中,然后将更新的内容直接写入到原始数据库;当发生崩溃或者回滚操作时,将一开始备份的数据放回到数据库文件中,以将数据库还原到其原始状态;当回滚日志文件被删除时,会触发提交操作。

wal模式恰恰相反;保留原始的数据库文件,将更新的内容append到一个单独的wal文件中;当有特殊的标记表明事务提交操作的内容append到wal文件时,就会触发提交操作;这样,在没有改动原始数据文件的情况下就发生了提交操作;更新的内容只是提交到了wal文件,所以其他读进程仍然可以继续操作原始的数据库文件;多个事务的更新都是append到同一个wal文件中的。

检查点

最终事务提交到wal文件的内容还是要更新到原始数据文件的,这一过程称为“检查点”;

在回滚日志方法中,有2个操作:读和写;而在wal模式中,有3个操作:读、写和检查点;

默认情况下,当wal文件达到阈值1000页(可以通过编译选项SQLITE_DEFAULT_WAL_AUTOCHECKPOINT设置)后,sqlite会自动执行检查,而不需要应用程序手动执行;不过应用程序可以调整阈值,或者关闭自动检查功能,然后手动执行检查。

并发性

在wal模式下,当一个读取操作到来时,它会首先记住上一次发生在wal文件的有效提交点,我们把这个点成为“end mark”;wal文件大小是不断增长的,原因是不同的连接都有可能会发生事务的提交,他们可能都有自己的“end mark”;不过,对于每个连接来说,在事务期间他们的“end mark”都是不变的,从而确保单个读取事务只看到数据库内容在某个时间点上的存在。

当一个读操作需要读取某个页的内容时,它首先会检查wal文件中是否存在该页,如果存在,则直接拉取“end mark”前的页,如果不存在,则从原始数据库文件读取;由于每个连接可以是由不同的进程发起的,为了避免每个连接都扫描wal去寻找页(wal文件大小可能会长到很大,这取决于检查点的频率),共享内存里面有一个叫做“wal-index”的数据结构,可以帮助快速的找到页的位置,并且耗费了极小的IO;不过这就使得发起连接的进程都必须在同一台机器上,这就是为什么wal模式无法在网络文件系统上工作的原因。

写操作仅仅只是把新内容append到wal文件,不影响读操作,所以读和写可以同时进行;然而,由于只有一个wal文件,所以同一时间只能有一个写操作。

检查点操作从wal文件取数据放到原始数据库文件中,检查点可以和读操作同时进行;但是,如果检查点超过任何一个读操作的“end mark”时,那么检查点操作必须停止下来,否则可能会覆盖读操作正在使用的页;通过共享内存的“wal-index”,检查点会记住它传输了哪些页到原始数据库文件,然后在下次执行检查点操作时将剩余的页继续传输到原始数据库文件中;因此,长时间运行的读操作可能会阻止检查点的进行,但是,所有的读操作最后都会结束,检查点因此得以继续进行。

任何时候写操作到来时,都会检查检查点的进行程度,如果整个的wal文件都被传输到原始数据库并且完成同步,那么如果此时没有读操作,则写操作会重置检查点到wal文件最开始,并且开始新的事务;通过这个机制,可以阻止wal文件的无限制增长。

性能分析

写事务非常快,因为他们仅仅只有一次写内容(回滚日志模式有2次写),而且是顺序写;只要开发者愿意在断点或者硬重启后牺牲持久性,还可以省略同步到磁盘的步骤(如果PRAGMA synchronous被设置为FULL,会在每次提交事务时同步WAL,但是如果PRAGMA synchronous被设置为NORMAL,则忽略这个同步。)

另一方面,每个读操作都必须检查wal文件的页,而且检查的时间跟wal文件的大小成正比;虽然内存共享可以帮助快速的查找wal文件的页,但是wal文件的大小仍然会降低查找的速度;因此,适当定期的运行检查点是很重要的。

检查点确实需要同步wal文件到原始数据库以防止断点或者硬重启导致的数据库损坏;检查点尽可能的按顺序页同步wal文件(传输到原始数据库的wal文件页是升序的),但是还是有很多查找操作穿插在同步wal之间;这些因素结合在一起就导致了检查点要比写事务慢得多。

默认策略是允许连续的写事务将wal文件大小增长到大约1000页,之后每个事务提交都会运行检查点,直到wal文件减小到1000页以下;默认情况下,检查点将由执行事务提交的线程自动执行,该线程会将wal推过其大小限制;通过这一策略,使得绝大部分的事务提交操作变得非常快,但是wal文件超过大小限制后触发的检查点会使得事务提交变得非常慢;如果不希望出现这种现象,开发者可以禁用自动检查点,然后在另外的线程或者进程中定期的进行检查点操作。

注意,如果PRAGMA synchronous被设置为NORMAL,那么检查点成为唯一触发IO繁忙或者同步文件的操作;因此,如果开发者在单独的线程或者进程中运行检查点,那么发起查询和更新操作的线程或者进程永远不会阻塞wal的同步;这可以防止因磁盘繁忙而出现的应用程序挂起现象;这样配置的缺点就是,事务不再持久性,在发生断电或者重启后可能出现事务回滚的现象。

同时也要注意,需要权衡读和写的性能;如果想最大限度提高读性能,则应该让wal文件尽可能的小,经常运行检查点,甚至可以每次事务提交就运行一次;如果想最大限度提高写性能,则应该减少检查点的频次,在执行检查点前可以让wal文件尽可能的大;默认策略是wal文件达到1000页大小的时候执行检查点,该策略在工作站的测试程序中表现最佳,但是可能并不适合其他应用程序。

配置wal模式

默认的模式是delete,输入

pragma journal_mode;

即可查看当前数据库的模式,输入

pragma journal_mode=wal;

即可改变数据库的模式,如果成功的话会返回wal,否则返回之前的模式;

自动检查点

默认情况下,任何时候当一个事务提交导致wal文件大小超过1000页或者更多,或者当操作数据库文件的最后一个连接关闭时,都会自动触发检查点,该配置适合大部分应用程序;不过,如果想更多的控制,则可以通过PRAGMA database_name.wal_checkpoint;或者调用sqlite3_wal_checkpoint() 接口来强制执行检查点;通过PRAGMA wal_autocheckpoint;或者调用sqlite3_wal_autocheckpoint()接口可以改变自动检查点阈值,甚至直接禁止自动检查;应用程序还可以通过sqlite3_wal_hook()接口注册回调函数,该回调函数在事务提交的时候会被调用,回调函数内可以在合适时机调用sqlite3_wal_checkpoint() 或者 sqlite3_wal_checkpoint_v2()接口(自动检查机制实际上也是围绕sqlite3_wal_hook()实现的)。

应用程序启动的检查点

应用程序可以通过调用sqlite3_wal_checkpoint() or sqlite3_wal_checkpoint_v2()来启动检查点,需要有可写数据库权限;检查点有3种类型:PASSIVE, FULL, and RESTART,默认是PASSIVE,它可以在不干扰其他数据库连接的情况下,尽可能完成更多的工作,但是如果存在并发读和写,可能不会运行检查点到完成;通过sqlite3_wal_checkpoint()启动的检查点,以及自动机制检查点,都是PASSIVE类型;FULL和RESTART类型则会尽可能的运行检查点到完成,而且只能通过sqlite3_wal_checkpoint_v2()调用来启动;

wal模式的持久性

不像其他模式的短暂性,wal模式是持久性的;如果一个连接设置了数据库文件为wal模式,则关闭、再打开数据库,模式还是wal;其他模式则相反,如果在一个连接中设置了该模式,那么当关闭、再打开时,数据库文件恢复到之前的模式,也就是说其他模式的生命期是一个连接周期。

wal文件

如果数据库文件模式是wal,那么当打开数据库时(实际测试发现是发起查询语句时),sqlite会自动生成一个跟数据库文件同名,并且带“-wal”后缀的文件,成为“写前日志”;该日志文件名可以在编译的时候通过参数改变;

只要有数据库连接,就会有wal文件,直到所有连接关闭,才会被删除掉;如果最后一次连接没有正确的关闭数据库,或者配置了SQLITE_FCNTL_PERSIST_WAL,那么wal文件会被保留在磁盘中;wal文件是数据库状态的一部分,如果拷贝或者移动数据库文件时,应该同步拷贝或者移动wal文件;如果数据库丢失了wal文件,那么事务提交的内容可能会丢失,数据库文件也有可能被损坏;删除wal文件唯一最安全的方法就是正常关闭数据库连接(sqlite3_close());

只读数据库

在version 3.22.0 (2018-01-22)版本之前,无法打开只读权限的wal模式数据库;

新版本之后,以下几种情况仍然可以打开只读权限数据库:

  1. -shm文件和-wal文件已经存在,并且可读;
  2. 数据库文件所在目录有可写权限,这样-shm文件和-wal文件就可以被创建了;
  3. 打开数据库时,带了immutable查询参数;

尽管可以打开只读WAL模式数据库,但是在将SQLite数据库映像烧录到只读磁盘之前,最好将其转换为PRAGMA journal_mode=DELETE模式。

避免wal文件过大

正常情况下,新的内容会append到wal文件中,直到增长到大约1000页(大概4M),会自动运行检查点以使wal文件可以循环利用;检查点不会清空wal文件(除非设置了PRAGMA schema.journal_size_limit;),而是重新开始覆盖wal文件,这是因为正常情况下覆盖写要比append写的速度要快;当最后一个连接关闭时,执行最后的检查点,然后删除wal文件以及关联的shm文件。

所以大部分情况下,开发者不需要关心wal文件,sqlite自动注意并检查着;但是这也有可能会导致wal文件无限制的增长,耗尽磁盘可用空间以及降低查询速度,下面列出了一些可能发生这种现象的操作以及如何避免它们:

  • 关掉自动检查机制----默认机制是达到1000页大小后自动执行检查点,但是编译选项和查询选项都可以关掉或者延迟自动检查点,这样会导致wal文件无限制增长;
  • 检查点机制的缺陷----如果没有其他数据库连接在使用wal文件的话,检查点会运行到完成,并重置wal文件;而如果有其他连接在读事务,那么检查点将无法重置wal文件,因为读事务的检查点在这之前,如果重置wal文件的话,读事务的内容将是错误的;检查点在不影响读事务的前提下,会尽可能多的工作,但是无法运行到完成,在下一次写事务后,检查点又在它停止的位置继续工作,这样往复,直到检查点能运行到完成为止;这时候如果很多并发操作,并且一直存在至少一个读事务,那么检查点将永远无法完成,导致wal文件无限制增大;这种情景只有想办法腾出这样的一个时间段:没有任何的读事务,然后运行检查点;在读事务并发很大的应用程序中,可以考虑利用SQLITE_CHECKPOINT_RESTART or SQLITE_CHECKPOINT_TRUNCATE选项手动让检查点运行到完成,SQLITE_CHECKPOINT_RESTART or SQLITE_CHECKPOINT_TRUNCATE选项的缺点是当检查点运行时,读事务可能会被阻塞。
  • 很庞大的写事务----只有当没有其他任何运行的事务时,检查点才能运行完成,意味着不能在写事务的中途去重置wal文件,所以对于一个庞大的数据库文件来说,如果事务更新的数据很大,将会导致wal文件非常大。从version 3.11.0 (2016-02-15)开始,单个事务的wal文件的大小应该与事务本身成正比,事务更新的页面应该只写入一次wal文件;然而,对于老版本的sqlite,如果事务增长到大于页面缓存,则相同的页面可能会多次写入wal文件。

WAL-Index共享内存的实现

wal-index是通过一个为了健壮性而映射成的普通文件;预发布版中,wal-index是存储在易失性共享内存中的,例如linux的/dev/shm或者unix的/tmp;这样导致的问题是拥有不同root目录的进程(通过chroot)会得到不同的共享内存区域,导致数据库不完整;其他方法,比如创建匿名的共享内存块,这种方法没办法在各种版本的unix上移植,另外也没有任何办法在Windows上创建匿名共享内存块;唯一的方法只有在数据库文件相同目录下映射一个共享内存文件。

使用普通磁盘文件来提供共享内存的方法有个缺点是将共享内存数据写入到磁盘会导致不必要的磁盘IO;然而,开发者们并不认为需要太关心这个问题,因为wal-index很少会超过32KB大小,并且永远不需要同步;另外,当最后一个连接关闭时,会删除掉wal-index文件,这通常会阻止掉真正的磁盘IO的发生。

对于不能个别应用程序,如果觉得默认的共享内存实现方法不合适,则可以通过VFS(https://www.sqlite.org/vfs.html)设定出其他方法;例如,如果一个数据库文件只会被单个进程中的线程访问,则可以通过堆内存而不是共享内存来实现wal-index。

去掉共享内存

从version 3.7.4 (2010-12-07)开始,在第一个连接到来前,只要设定了PRAGMA schema.locking_mode=EXCLUSIVE; 即使共享内存不可用,wal模式数据库仍然可以被创建、读取、写入;换句话说,如果能保证访问数据库的进程是唯一的,则该进程不需要共享内存就可以与数据库交互;

只有在第一次访问数据库之前可以设定PRAGMA schema.locking_mode=EXCLUSIVE;只要设定了该选项,sqlite将永远不会调用任何的共享内存方法,这样wal-index就不会被创建了;只要数据库模式是wal,那么将一直保持EXCLUSIVE模式,就算调用PRAGMA schema.locking_mode=NORMAL;也不会生效,唯一方法是先将数据库模式由wal转为其他模式。

如果PRAGMA schema.locking_mode=NORMAL;那么共享内存wal-index会被创建,这意味着底层的VFS必须支持“version 2”的共享内存,否则将无法打开wal模式数据库,也无法将数据库从其他模式更改为wal模式;对于数据库连接,只要使用了内存共享wal-index,locking mode就可以在NORMAL and EXCLUSIVE之间自由切换;只有当忽略共享内存wal-index,且在第一次访问数据前设定locking mode为EXCLUSIVE,才会锁定在EXCLUSIVE模式。

有时候返回SQLITE_BUSY

前面说到,wal模式的优点之一是读写互不影响,这只是大部分情况,也有少数情况下可能会返回SQLITE_BUSY,所以我们应该为发生这种情况做好准备。以下几种场景可能会导致这种情况发生:

  • 如果数据库连接以PRAGMA schema.locking_mode=EXCLUSIVE;模式打开,那么其他所有的连接都会返回SQLITE_BUSY;谷歌和火狐浏览器都是以EXCLUSIVE模式打开数据库的;
  • 当最后一个连接正在关闭时,它会获得一个短暂的排他锁(清理wal文件和shm文件),如果该连接正处在这个排他锁的时间段内,那么,如果有第二个连接正在试图打开数据库,它将会收到SQLITE_BUSY;
  • 如果最后一个连接发生错误而异常退出了,那么下一次第一个打开数据库的连接将会进行修复,修复期间会获得一个排他锁;这时候如果有其他连接试图打开数据库,它将会收到SQLITE_BUSY;

向后兼容性

wal模式的数据库文件格式没有发生变化,但是wal文件和wal-index是新的概念,旧版的sqlite无法恢复发生崩溃的wal模式数据库;为了防止旧版的sqlite(version 3.7.0,2010-07-22之前)试图恢复wal模式的数据库,数据库文件格式版本号(位于数据库头部的18和19字节)从1增加到2;这样,如果旧版的sqlite试图恢复wal模式数据库时,将收到一个错误“file is encrypted or is not a database”。

不过,可以改变数据库模式,PRAGMA journal_mode=DELETE;这样数据库文件格式版本号又恢复为1,旧版的sqlite就可以打开数据库了。

sqlite的wal模式相关推荐

  1. GRDB使用SQLite的WAL模式

    GRDB使用SQLite的WAL模式 WAL全称是Write Ahead Logging,它是SQLite中实现原子事务的一种机制.该模式是从SQLite 3.7.0版本引入的.再此之前,SQLite ...

  2. WAL模式 Android,SQLite 数据库 WAL 工作模式原理简介

    闪存 对象序列化系列 数据序列化系列(待更) <Android 数据序列化之 JSON> <Android 数据序列化之 Protocol Buffer 使用> <And ...

  3. android wal模式,WCDB 的 WAL 模式和异步 Checkpoint

    WAL 模式是 SQLite 3.7.0 版本推出的改进写性能和并发性的功能,至今已经7年多了,但由于WAL是默认关闭的,可能有相当多的应用并没有用上,仍然使用性能较差的传统模式. 微信 APP 开启 ...

  4. WAL模式 Android,Android使用SQLITE3 WAL模式

    sqlite是支持writeaheadlogging(WAL)模式的,开启WAL模式可以提高写入数据库的速度,读和写之间不会阻塞,但是写与写之间依然是阻塞的,但是如果使用默认的TRUNC sqlite ...

  5. 数据库 wal 模式 分析

    对于以下错误的解决思路 看我的另外一篇文章 https://blog.csdn.net/tianxuhong/article/details/78752357 android.database.sql ...

  6. sqlite 怎么开启wal机制

    Sqlite在多线程下的使用方法及注意的事项 一Sqlite的三种模式 1.      单线程,这种模式下,没有进行互斥,多线程使用不安全 2.      多线程,这种模式下,在多线程中使用单个数据库 ...

  7. CoreData数据库探索

    CoreData是什么 Core Data 是苹果公司提供的一个对象-关系映射框架(Object-Relational Mapping,ORM),用于管理应用程序的数据模型.Core Data 提供了 ...

  8. SQLite WAL 机制探索

    什么是WAL机制 SQLite 的 WAL(Write-Ahead Logging)机制是一种高效的事务日志机制,用于将修改操作写入一个独立的 .sqlite-wal 文件中,而不是直接写入主数据库文 ...

  9. sqlite3:锁机制、stmt加速、wal日志模式、多进程并发、写互斥

    最近需要做sqlite的并发优化,会有一些多主机多进程的操作失败问题,所以学习一下,顺便为了翻阅,做一个笔记收集. 未完成................... to be continued 目前只 ...

最新文章

  1. 二叉树的非递归遍历(递归和非递归)
  2. fasta.img 是什么文件?
  3. struts2 中文乱码问题
  4. java死锁的产生与解决
  5. Spring整合基础
  6. LeetCode 551. Student Attendance Record I
  7. “弃用 Google AMP!”
  8. python数据类型二(列表和元组)
  9. 手机号空号检测的几点建议
  10. #STM32 LCD12864编程即原理介绍
  11. cydia无法安装卸载插件_简单 用文件管理器干掉Cydia顽固插件源
  12. android studio设置安卓版本,android studio怎么设置android版本?
  13. 和华明诚教育:店铺降权应该要怎么样优化
  14. Windows 变慢原因分析及解决方法
  15. 百度网盘网页倍速播放
  16. 可扩展性类毕业论文文献都有哪些?
  17. 艾永亮:盘点产品创新的三种颠覆方式
  18. Android 获取手机系统版本号、获取手机型号、获取手机厂商、获取手机IMEI、获取手机CPU_ABI、获取手机唯一识别码
  19. php邀请码系统源码,Dede织梦生成会员注册邀请码插件 - 猿码网
  20. 索尼电脑安装linux,Sony Vaio P 安装Ubuntu Netbook Remix

热门文章

  1. WordAnyTime 1.0.0发布
  2. rust的矿坑_转: Rust中的Pin详解 【Rust语言中文社区】
  3. 整理css基础篇-定位(常规+浮动+定位)
  4. 告诉你用Python赚钱的五种方法,闲余月赚1000~5000
  5. Java/Android中的引用类型及WeakReference应用实践
  6. 太极博弈原理学生读后感之先归宗再演化
  7. 用mixly软件解决esp8366,OLED液晶屏显示网络时钟的只显示个位数的问题
  8. 数据库系统概念:CH14 事务Transactions
  9. 离婚时婚后父母出资所购房屋分割规则
  10. win系统重装系统后提示 BitLocker(磁盘加密)密钥查找及如何关闭