SQLite 只支持一个并发的写入操作,但是多个进程可以同时连接和查询相同的数据库。
通过一些简单的配置和操作,我们完全可以使用 SQLite 创建 GB 级别的数据库并且支持高达每秒 10 万次的并发查询。
优化 SQLite 性能的配置如下:
pragma journal_mode = WAL;
使用WAL而不是回滚日志有利有弊。优点包括:
在大多数情况下,WAL明显更快。
WAL提供更多的并发性,因为读者不会阻止编写者,而编写者也不会阻止读者。阅读和写作可以同时进行。
使用WAL,磁盘I / O操作往往更顺序。
WAL使用更少的fsync()操作,因此在fsync()系统调用被破坏的系统上不易受到问题的影响。
但也有缺点:
WAL通常要求VFS 支持共享内存原语。(例外:没有共享内存的WAL)内置的unix和Windows VFS支持此功能,但自定义操作系统的第三方扩展VFS可能不支持。
使用数据库的所有进程必须位于同一台主机上; WAL无法在网络文件系统上运行。
涉及针对多个ATTACHed 数据库的更改的事务对于每个单独的数据库都是原子的,但在所有数据库中作为集合不是原子的。
进入WAL模式后,无法在空数据库上或使用VACUUM或使用备份API从备份恢复时更改page_size。您必须处于回滚日志模式才能更改页面大小。
无法打开只读WAL数据库。如果该文件存在,则打开过程必须具有与数据库关联的" -shm " wal-index共享内存文件的写权限,如果" -shm "文件不存在,则必须对包含数据库文件的目录具有写访问权限。 从版本3.22.0(2018-01-22)开始,如果-shm和-wal文件已存在或者可以创建这些文件或 数据库是不可变的,则可以打开只读WAL模式数据库文件。
在大多数读取和很少写入的应用程序中,WAL可能比传统的rollback-journal方法稍慢(可能慢1%或2%)。
还有一个与每个数据库关联的额外准持久性" -wal "文件和" -shm "共享内存文件,这可能使SQLite不太适合用作 应用程序文件格式。
检查点的额外操作虽然默认是自动的,但仍然是应用程序开发人员需要注意的事情。
WAL适用于较小的交易。WAL对于非常大的事务不适用。对于大于约100兆字节的事务,传统的回滚日志模式可能会更快。对于超过千兆字节的事务,WAL模式可能会因I / O或磁盘已满错误而失败。建议将其中一种回滚日志模式用于大于几十兆字节的事务。 从版本3.11.0(2016-02-15)开始,WAL模式与大型事务一样有效,与回滚模式一样。
为什么会报错呢, 原因是手机默认开启了wal 模式, 但是我们读取的db 文件及其目录不可写, 导致,系统在进行数据库的wal 操作,打开k.db-wal 文件时, 该临时文件不能创建, 导致 执行的事物操作中断。
这些源码都说明了一个问题, 开启wal 模式需要对db 文件所在的目录可写权限。
pragma synchronous = normal;
synchronous 的默认值为 full,意味着每个更新操作都需要等待 FSYNC 完成内存数据到磁盘文件的同步。在 WAL 模式下,normal 方式仍然是完全安全的,同时意味着只有 WAL 检查点操作需要等待 FSYNC 完成。off 可能导致数据库损坏,虽然我们极少遇到这种问题。
pragma temp_store = memory;
以上指令表示使用内存存储临时索引和表。SQLite 可以自动为某些查询语句创建临时索引。无法确定这种操作可以提供多少性能帮助,如果 SQLite 创建了临时索引(通过 EXPLAIN QUERY PLAN 命令查看),我们应该主动创建相应的索引。
对于存储大型 BLOB 数据而言,增加页面大小可以改进数据库的性能;但是对于一般的应用场景而言,这种方法可能并不适用。对于写入操作,SQLite 总是替换整个页面,因此增加页面大小会增加写入操作的开销。
当数据库的大小小于 mmap_size 字节时使用内存映射替代读/写调用。操作系统管理的 syscall、页面以及缓存将会更少,此时的性能取决于使用的操作系统。注意,该设置不会使用物理内存,而是保留虚拟内存。然后,操作系统将根据常用的"磁盘缓存"逻辑决定哪些页面被逐出,哪些页面留在内存中。至少是在 Linux 上,如果为 SQLite 进程分配了足够的内存,性能将会得到明显提升。如果数据库的大小比指定的 mmap_size 更大,数据库的一部分将会使用内存映射,其他仍然使用 read() / write() 系统调用。
pragma mmap_size = 30000000000;
对于以下错误的解决思路
看我的另外一篇文章
https://blog.csdn.net/tianxuhong/article/details/78752357
android.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database file (code 14)
▼ 目录
1.概述
- WAL如何工作
2.1。检查点
2.2。并发
2.3。性能注意事项
3.激活和配置WAL模式
3.1。自动检查点
3.2。应用程序启动的检查点
3.3。WAL模式的持久性
- WAL文件
5.只读数据库
6.避免过大的WAL文件
- WAL-Index共享内存的实现
8.使用没有共享内存的WAL
9.有时查询在WAL模式下返回SQLITE_BUSY
10.向后兼容性
1.概述
SQLite实现原子提交和回滚的默认方法 是回滚日志。从版本3.7.0(2010-07-21)开始,可以使用新的"预写日志"选项(以下称为"WAL")。
使用WAL而不是回滚日志有利有弊。优点包括:
在大多数情况下,WAL明显更快。
WAL提供更多的并发性,因为读者不会阻止编写者,而编写者也不会阻止读者。阅读和写作可以同时进行。
使用WAL,磁盘I / O操作往往更顺序。
WAL使用更少的fsync()操作,因此在fsync()系统调用被破坏的系统上不易受到问题的影响。
但也有缺点:
WAL通常要求VFS 支持共享内存原语。(例外:没有共享内存的WAL)内置的unix和Windows VFS支持此功能,但自定义操作系统的第三方扩展VFS可能不支持。
使用数据库的所有进程必须位于同一台主机上; WAL无法在网络文件系统上运行。
涉及针对多个ATTACHed 数据库的更改的事务对于每个单独的数据库都是原子的,但在所有数据库中作为集合不是原子的。
进入WAL模式后,无法在空数据库上或使用VACUUM或使用备份API从备份恢复时更改page_size。您必须处于回滚日志模式才能更改页面大小。
无法打开只读WAL数据库。如果该文件存在,则打开过程必须具有与数据库关联的" -shm " wal-index共享内存文件的写权限,如果" -shm "文件不存在,则必须对包含数据库文件的目录具有写访问权限。 从版本3.22.0(2018-01-22)开始,如果-shm和-wal文件已存在或者可以创建这些文件或 数据库是不可变的,则可以打开只读WAL模式数据库文件。
在大多数读取和很少写入的应用程序中,WAL可能比传统的rollback-journal方法稍慢(可能慢1%或2%)。
还有一个与每个数据库关联的额外准持久性" -wal "文件和" -shm "共享内存文件,这可能使SQLite不太适合用作 应用程序文件格式。
检查点的额外操作虽然默认是自动的,但仍然是应用程序开发人员需要注意的事情。
WAL适用于较小的交易。WAL对于非常大的事务不适用。对于大于约100兆字节的事务,传统的回滚日志模式可能会更快。对于超过千兆字节的事务,WAL模式可能会因I / O或磁盘已满错误而失败。建议将其中一种回滚日志模式用于大于几十兆字节的事务。 从版本3.11.0(2016-02-15)开始,WAL模式与大型事务一样有效,与回滚模式一样。
- WAL如何工作
传统的回滚日志通过将原始未更改的数据库内容的副本写入单独的回滚日志文件,然后将更改直接写入数据库文件来工作。如果发生崩溃或ROLLBACK,则回滚日志中包含的原始内容将回放到数据库文件中,以将数据库文件还原为其原始状态。该COMMIT当回滚日志被删除时。
WAL方法将此反转。原始内容保留在数据库文件中,更改将附加到单独的WAL文件中。甲COMMIT当指示一个提交一个特殊的记录被追加到WAL发生。因此,COMMIT可以在不写入原始数据库的情况下发生,这允许读者在将更改同时提交到WAL中时继续从原始未更改的数据库进行操作。多个事务可以附加到单个WAL文件的末尾。
2.1。检查点
当然,人们希望最终将WAL文件中附加的所有事务传回原始数据库。将WAL文件事务移回数据库称为" 检查点 "。
考虑回滚和预写日志之间区别的另一种方法是,在rollback-journal方法中,有两个基本操作,即读取和写入,而使用预写日志,现在有三个基本操作:读取和写入和检查点。
默认情况下,当WAL文件达到1000页的阈值大小时,SQLite会自动执行检查点。( SQLITE_DEFAULT_WAL_AUTOCHECKPOINT编译时选项可用于指定不同的默认值。)使用WAL的应用程序不必执行任何操作以使这些检查点发生。但如果他们愿意,应用程序可以调整自动检查点阈值。或者他们可以在空闲时刻或单独的线程或过程中关闭自动检查点并运行检查点。
2.2。并发
当在WAL模式数据库上开始读取操作时,它首先会记住WAL中最后一个有效提交记录的位置。称此为"结束标记"。由于WAL可以在各种读取器连接到数据库时增长并添加新的提交记录,因此每个读取器都可能具有自己的结束标记。但对于任何特定的读者,结束标记在事务持续期间不变,从而确保单个读取事务仅查看单个时间点存在的数据库内容。
当读者需要一页内容时,它首先检查WAL以查看该页面是否出现,如果是,它会在读取器结束标记之前拉入WAL中发生的页面的最后一个副本。如果在读取器结束标记之前WAL中没有页面副本,则从原始数据库文件中读取页面。读者可以存在于不同的进程中,因此为了避免强迫每个读者扫描整个WAL寻找页面(WAL文件可以增长到几兆字节,具体取决于检查点运行的频率),称为"wal-index"的数据结构保存在共享内存中,帮助读者快速定位WAL中的页面,并且I / O最少。wal-index极大地提高了读者的性能,但共享内存的使用意味着所有读者必须存在于同一台机器上。
作者只是将新内容附加到WAL文件的末尾。因为作家不做任何会干扰读者行为的事情,作家和读者可以同时运行。但是,由于只有一个WAL文件,因此一次只能有一个写入器。
检查点操作从WAL文件中获取内容并将其传回原始数据库文件。检查点可以与读取器同时运行,但检查点必须在到达WAL中超过任何当前读取器结束标记的页面时停止。检查点必须在此时停止,否则它可能会覆盖读取器正在使用的部分数据库文件。检查点会记住(在wal-index中)它到达了多远,并将继续将内容从WAL传输到数据库,从下次调用时停止。
因此,长时间运行的读取事务可以防止检查指针进行。但据推测,每次读取事务最终都会结束,并且checkpointer将能够继续。
每当发生写操作时,编写器检查checkpointer进行了多少进度,如果整个WAL已经转移到数据库并同步,如果没有读者正在使用WAL,那么编写器将把WAL倒回到开始并开始在WAL的开头放置新的交易。此机制可防止WAL文件无限制地增长。
2.3。性能注意事项
写入事务非常快,因为它们只涉及一次写入内容(而不是两次用于回滚日志事务),并且因为写入都是顺序的。此外,只要应用程序愿意在断电或硬重启后牺牲耐用性,就不需要将内容同步到磁盘。(如果PRAGMA synchronous设置为FULL,则写入者在每次事务提交时同步WAL, 但如果PRAGMA synchronous设置为NORMAL,则省略此同步 。)
另一方面,随着WAL文件的大小增加,读取性能下降,因为每个读取器必须检查WAL文件中的内容,并且检查WAL文件所需的时间与WAL文件的大小成比例。wal-index有助于更快地找到WAL文件中的内容,但随着WAL文件大小的增加,性能仍会下降。因此,为了保持良好的读取性能,通过定期运行检查点来保持WAL文件大小不变是很重要的。
检查点确实需要同步操作,以避免在断电或硬重启后数据库损坏的可能性。在将内容从WAL移动到数据库之前,必须将WAL同步到持久存储,并且必须在重置WAL之前同步数据库文件。检查站还需要更多的追求。checkpointer努力尽可能多地对数据库执行顺序页面写入(页面按照升序从WAL传输到数据库),但即便如此,页面写入中通常会有许多搜索操作。这些因素结合起来使检查点比写入事务更慢。
默认策略是允许连续写入事务使WAL增长,直到WAL变为大约1000页,然后为每个后续COMMIT运行检查点操作,直到WAL重置为小于1000页为止。默认情况下,检查点将由执行COMMIT的同一线程自动运行,该线程将WAL推过其大小限制。这会导致大多数COMMIT操作非常快,但偶尔的COMMIT(触发检查点的那些)要慢得多。如果不希望出现这种影响,那么应用程序可以禁用自动检查点并在单独的线程或单独的进程中运行定期检查点。(完成此任务的命令和接口链接 如下所示。)
请注意,在PRAGMA同步设置为NORMAL的情况下,检查点是发出I / O屏障或同步操作的唯一操作(Windows上的unix或FlushFileBuffers()上的fsync())。因此,如果应用程序在单独的线程或进程中运行检查点,则执行数据库查询和更新的主线程或进程将永远不会阻止同步操作。这有助于防止在繁忙的磁盘驱动器上运行的应用程序中出现"闩锁"。此配置的缺点是事务不再持久,并且可能在电源故障或硬重置后回滚。
另请注意,平均读取性能和平均写入性能之间存在折衷。为了最大化读取性能,人们希望保持WAL尽可能小,因此经常运行检查点,可能与每个COMMIT一样频繁。为了最大限度地提高写入性能,人们希望通过尽可能多的写入来分摊每个检查点的成本,这意味着想要不经常运行检查点并让WAL在每个检查点之前尽可能大。因此,根据应用程序的相对读写性能要求,决定运行检查点的频率可能因应用程序而异。WAL达到1000页后,默认策略是运行检查点,这种策略似乎在工作站上的测试应用程序中运行良好,
3.激活和配置WAL模式
SQLite数据库连接默认为 journal_mode = DELETE。要转换为WAL模式,请使用以下编译指示:
PRAGMA journal_mode = WAL;
journal_mode pragma返回一个字符串,它是新的日志模式。成功时,pragma将返回字符串" wal "。如果无法完成到WAL的转换(例如,如果VFS 不支持必要的共享内存原语),则日记模式将保持不变,并且从原语返回的字符串将是先前的日记模式(例如" 删除 ")。
3.1。自动检查点
默认情况下,每当发生COMMIT 导致WAL文件大小为1000页或更大,或者数据库文件上的最后一个数据库连接关闭时,SQLite将自动检查点。默认配置适用于大多数应用程序。但是需要更多控制的程序可以使用wal_checkpoint pragma或调用sqlite3_wal_checkpoint() C接口强制检查点。可以使用wal_autocheckpoint pragma或通过调用 sqlite3_wal_autocheckpoint() C接口来更改自动检查点阈值或完全禁用自动检查点。程序也可以使用sqlite3_wal_hook()注册任何事务提交到WAL时要调用的回调。然后,此回调可以根据其认为合适的标准调用 sqlite3_wal_checkpoint()或sqlite3_wal_checkpoint_v2()。(自动检查点机制是作为sqlite3_wal_hook()的简单包装器实现的。)
3.2。应用程序启动的检查点
应用程序只需调用sqlite3_wal_checkpoint()或sqlite3_wal_checkpoint_v2()即可使用数据库上的任何可写数据库连接启动检查点 。检查点有三种子类型,其攻击性各不相同:PASSIVE,FULL和RESTART。默认检查点样式是PASSIVE,它可以在不干扰其他数据库连接的情况下尽可能多地工作,如果存在并发读取器或编写器,则可能无法完成。由sqlite3_wal_checkpoint()和自动检查点机制启动的所有检查点都是PASSIVE。FULL和RESTART检查点更难以运行检查点完成,只能通过调用sqlite3_wal_checkpoint_v2()来启动。有关FULL和RESET检查点的其他信息,请参阅 sqlite3_wal_checkpoint_v2()文档。
3.3。WAL模式的持久性
与其他日记模式不同, PRAGMA journal_mode = WAL是持久的。如果进程设置WAL模式,然后关闭并重新打开数据库,则数据库将以WAL模式返回。相反,如果进程设置(例如)PRAGMA journal_mode = TRUNCATE然后关闭并重新打开,则数据库将以DELETE的默认回滚模式而不是之前的TRUNCATE设置重新启动。
WAL模式的持久性意味着可以在WAL模式下将应用程序转换为使用SQLite,而无需对应用程序本身进行任何更改。只需使用命令行shell或其他实用程序在数据库文件上运行" PRAGMA journal_mode = WAL; " ,然后重新启动应用程序。
如果在任何一个连接上设置了WAL日志模式,则将在同一数据库文件的所有连接上设置WAL日志模式。
- WAL文件
在WAL模式数据库上打开数据库连接时,SQLite维护一个名为"Write Ahead Log"或"WAL File"的额外日志文件。磁盘上此文件的名称通常是带有额外" -wal "后缀的数据库文件的名称,但如果使用SQLITE_ENABLE_8_3_NAMES编译SQLite,则可能会应用不同的命名规则。
只要任何数据库连接打开数据库,WAL文件就存在。通常,当最后一次连接数据库关闭时,WAL文件会自动删除。但是,如果打开数据库的最后一个进程退出而没有干净地关闭数据库连接,或者如果 SQLITE_FCNTL_PERSIST_WAL 文件控制使用,然后在关闭所有数据库连接后,WAL文件可能会保留在磁盘上。WAL文件是数据库持久状态的一部分,如果复制或移动数据库,则应与数据库保持一致。如果数据库文件与其WAL文件分离,则先前提交到数据库的事务可能会丢失,或者数据库文件可能已损坏。删除WAL文件的唯一安全方法是使用sqlite3_open()接口之一打开数据库文件,然后使用sqlite3_close()立即关闭数据库。
该WAL文件格式的精确定义,是跨平台的。
5.只读数据库
较旧版本的SQLite无法读取只读的WAL模式数据库。换句话说,为了读取WAL模式数据库,需要写访问。从SQLite 版本3.22.0(2018-01-22)开始放宽了这个约束。
在较新版本的SQLite上,只要满足以下一个或多个条件,仍然可以读取只读介质上的WAL模式数据库或缺少写入权限的WAL模式数据库:
该-shm和-wal文件已经存在,并且是可读
对包含数据库的目录具有写权限,以便可以创建-shm和-wal文件。
使用不可变查询参数打开数据库连接 。
尽管可以打开只读WAL模式数据库,但最好在将SQLite数据库映像刻录到只读介质之前转换为 PRAGMA journal_mode = DELETE。
6.避免过大的WAL文件
在正常情况下,新内容被附加到WAL文件,直到WAL文件累积大约1000页(因此大小约为4MB),此时检查点自动运行并且WAL文件被回收。检查点通常不会截断WAL文件(除非设置了journal_size_limit pragma)。相反,它只会导致SQLite从头开始覆盖WAL文件。这样做是因为覆盖现有文件通常比追加更快。当数据库的最后一个连接关闭时,该连接会执行最后一个检查点,然后删除WAL及其关联的共享内存文件,以清理磁盘。
所以在绝大多数情况下,应用程序根本不用担心WAL文件。SQLite会自动处理它。但是有可能让SQLite进入WAL文件无限制增长的状态,导致磁盘空间使用过多,查询速度变慢。以下项目符号列举了一些可能发生这种情况的方法以及如何避免这些方法。
禁用自动检查点机制。 在默认配置中,当WAL文件长度超过1000页时,SQLite将在任何事务结束时检查WAL文件。但是,存在可以禁用或推迟此自动检查点的编译时和运行时选项。如果应用程序禁用自动检查点,则没有什么可以阻止WAL文件过度增长。
检查站饥饿。 如果使用WAL文件没有其他数据库连接,则检查点只能运行完成并重置WAL文件。如果另一个连接打开了读取事务,则检查点无法重置WAL文件,因为这样做可能会从读取器下方删除内容。检查点将尽可能多地完成工作而不会扰乱读者,但它无法完成。在下一次写入事务之后,检查点将再次启动。这种情况一直持续到某些检查点能够完成为止。
但是,如果数据库具有许多并发重叠读取器并且始终至少有一个活动读取器,则无法检查点完成,因此WAL文件将无限制地增长。
通过确保存在"读取器间隙"可以避免这种情况:没有进程从数据库读取的时间以及在这些时间内尝试检查点的时间。在具有许多并发读取器的应用程序中,可以考虑使用SQLITE_CHECKPOINT_RESTART或SQLITE_CHECKPOINT_TRUNCATE选项运行手动检查点,这将确保检查点在返回之前运行完成。使用SQLITE_CHECKPOINT_RESTART和SQLITE_CHECKPOINT_TRUNCATE的缺点 是读者可能会在检查点运行时阻塞。
非常大的写入事务。 检查点只能在没有其他事务正在运行时完成,这意味着WAL文件无法在写入事务中重置。因此,对大型数据库进行大量更改可能会导致大型WAL文件。写入事务完成后,WAL文件将被检查点(假设没有其他读者阻止它),但与此同时,文件可能会变得非常大。
从SQLite 版本3.11.0(2016-02-15)开始,单个事务的WAL文件大小应与事务本身成比例。事务更改的页面只能写入WAL文件一次。但是,对于旧版本的SQLite,如果事务变得大于页面缓存,则可能会多次将同一页面写入WAL文件。
- WAL-Index共享内存的实现
在沃尔玛指数是否使用了mmapped健壮性一个普通文件实现。WAL模式的早期(预发布)实现将wal-index存储在易失性共享内存中,例如在Linux上的/ dev / shm或其他unix系统上的/ tmp中创建的文件。该方法的问题是具有不同根目录的进程(通过chroot更改))将看到不同的文件,因此使用不同的共享内存区域,导致数据库损坏。用于创建无名共享内存块的其他方法不能在各种版本的unix中移植。我们找不到任何方法来在Windows上创建无名共享内存块。我们发现保证访问同一数据库文件的所有进程使用相同共享内存的唯一方法是通过将文件映射到与数据库本身相同的目录来创建共享内存。
使用普通磁盘文件提供共享内存的缺点是,通过将共享内存写入磁盘,它实际上可能会执行不必要的磁盘I / O. 然而,开发人员并不认为这是一个主要问题,因为沃尔玛指数的规模很少超过32 KiB并且从未同步过。此外,当最后一个数据库连接断开连接时,将删除wal-index后备文件,这通常会阻止任何真正的磁盘I / O发生。
共享内存的默认实现不可接受的专用应用程序可以通过自定义VFS设计替代方法。例如,如果已知某个特定数据库只能由单个进程中的线程访问,则可以使用堆内存而不是真正的共享内存来实现wal-index。
8.使用没有共享内存的WAL
从SQLite 版本3.7.4(2010-12-07)开始,只要在第一次尝试访问之前将locking_mode设置为EXCLUSIVE,即使共享内存不可用,也可以创建,读取和写入WAL数据库 。换句话说,如果保证该进程是访问数据库的唯一进程,则进程可以与WAL数据库交互而不使用共享内存。此功能允许由缺少sqlite3_io_methods对象上的"版本2"共享内存方法xShmMap,xShmLock,xShmBarrier和xShmUnmap 的旧VFS创建,读取和写入WAL数据库。
如果 在第一次WAL模式数据库访问之前设置了EXCLUSIVE锁定模式,那么SQLite永远不会尝试调用任何共享内存方法,因此不会创建共享内存wal-index。在这种情况下,只要日志模式为WAL,数据库连接就保持在EXCLUSIVE模式; 尝试使用" PRAGMA locking_mode = NORMAL; " 更改锁定模式是no-ops。更改EXCLUSIVE锁定模式的唯一方法是首先更改WAL日志模式。
如果NORMAL锁定模式对第一个WAL模式数据库访问有效,则创建共享内存wal-index。这意味着底层VFS必须支持"版本2"共享内存。如果VFS不支持共享内存方法,则尝试打开已处于WAL模式的数据库或尝试将数据库转换为WAL模式将失败。只要一个连接使用共享内存wal-index,就可以在NORMAL和EXCLUSIVE之间自由更改锁定模式。仅当省略共享内存wal-index时,在第一个WAL模式数据库访问之前锁定模式为EXCLUSIVE时,锁定模式才会停留在EXCLUSIVE中。
9.有时查询在WAL模式下返回SQLITE_BUSY
WAL模式的第二个优点是编写者不会阻止读者和读者阻止编写者。这基本上是正确的。但是有一些模糊的情况,对WAL模式数据库的查询可以返回SQLITE_BUSY,因此应用程序应该为这种偶然事件做好准备。
针对WAL模式数据库的查询可以返回SQLITE_BUSY的情况 包括以下内容:
如果另一个数据库连接以独占锁定模式打开数据库模式,则对数据库的所有查询都将返回SQLITE_BUSY。Chrome和Firefox都以独占锁定模式打开数据库文件,因此在应用程序运行时尝试读取Chrome或Firefox数据库会遇到此问题。
当特定数据库的最后一个连接关闭时,该连接将在清理WAL和共享内存文件时获得短时间的独占锁。如果第二个数据库在第一个连接仍处于清理过程的中间时尝试打开并查询数据库,则第二个连接可能会收到SQLITE_BUSY 错误。
如果与数据库的最后一次连接崩溃,则第一个打开数据库的新连接将启动恢复过程。在恢复期间保持独占锁定。因此,如果第二个连接正在运行恢复时第三个数据库连接尝试跳入并进行查询,则第三个连接将收到SQLITE_BUSY错误。
10.向后兼容性
WAL模式的数据库文件格式保持不变。但是,WAL文件和wal-index是新概念,因此旧版本的SQLite将不知道如何恢复崩溃发生时在WAL模式下运行的崩溃的SQLite数据库。防止旧版本的SQLite(版本3.7.0,2010-07-22之前)尝试恢复WAL模式数据库(并且更糟糕)数据库文件格式版本号(数据库头中的字节18和19)在WAL模式下,从1增加到2。因此,如果旧版本的SQLite尝试连接到以WAL模式运行的SQLite数据库,它将报告"文件已加密或不是数据库"的错误。
可以使用如下的pragma显式更改出WAL模式:
PRAGMA journal_mode = DELETE;
故意更改出WAL模式会将数据库文件格式版本号更改回1,以便旧版本的SQLite可以再次访问数据库文件。
概述
在3.7.0以后,WAL(Write-Ahead Log)模式可以使用,是另一种实现事务原子性的方法。
WAL的优点
在大多数情况下更快
并行性更高。因为读操作和写操作可以并行。
文件IO更加有序化,串行化(more sequential)
使用fsync()的次数更少,在fsync()调用时好时坏的机器上较为未定。
缺点
一般情况下需要VFS支持共享内存模式。(shared-memory primitives)
操作数据库文件的进程必须在同一台主机上,不能用在网络操作系统。
持有多个数据库文件的数据库连接对于单个数据库时原子的,对于全部数据库是不原子的。
进入WAL模式以后不能修改page的size。
不能打开只读的WAL数据库(Read-Only Databases),这进程必须有"-shm"文件的写权限。
对于只进行读操作,很少进行写操作的数据库,要慢那么1到2个百分点。
会有多余的"-wal"和"-shm"文件
需要开发者注意checkpointing
原理
回滚日志的方法是把为改变的数据库文件内容写入日志里,然后把改变后的内容直接写到数据库文件中去。在系统crash或掉电的情况下,日志里的内容被重新写入数据库文件中。日志文件被删除,标志commit着一次commit的结束。
WAL模式于此此相反。原始为改变的数据库内容在数据库文件中,对数据库文件的修改被追加到单独的WAL文件中。当一条记录被追加到WAL文件后,标志着一次commit的结束。因此一次commit不必对数据库文件进行操作,当正在进行写操作时,可以同时进行读操作。多个事务的内容可以追加到一个WAL文件的末尾。
checkpoint
最后WAL文件的内容必须更新到数据库文件中。把WAL文件的内容更新到数据库文件的过程叫做一次checkpoint。
回滚日志的方法有两种操作:读和写。WAL有三种操作,读、写和checkpoint。
默认的,SQL会在WAL文件达到1000page时进行一次checkpoint。进行WAL的时机也可以由应用程序自己决定。
并发性
当一个读操作发生在WAL模式的数据库上时,会首先找到WAL文件中最后一次提交,叫做"end mark"。每一个事务可以有自己的"end point",但对于一个给定额事务来说,end mark是固定的。
当读取数据库中的page时,SQLite会先从WAL文件中寻找有没有对应的page,从找出离end mark最近的那一条记录;如果找不到,那么就从数据库文件中寻找对一个的page。为了避免每次事务都要扫描一遍WAL文件,SQLite在共享内存中维护了一个"wal-index"的数据结构,帮助快速定位page。
写数据库只是把新内容加到WAL文件的末尾,和读操作没有关系。由于只有一个WAL文件,因此同时只能有一个写操作。
checkpoint操作可以和读操作并行。但是如果checkpoint把一个page写入数据库文件,而且这个page超过了当前读操作的end mark时,checkpoint必须停止。否则会把当前正在读的部分覆盖掉。下次checkpoint时,会从这个page开始往数据库中拷贝数据。
当写操作时,会检查WAL文件被拷贝到数据库的进度。如果已经完全被拷贝到数据库文件中,已经同步,并且没有读操作在使用WAL文件,那么会把WAL文件清空,从其实开始追加数据。保证WAL文件不会无限制增长。
性能
写操作是很快的,因为只需要进行一次写操作,并且是顺序的(不是随机的,每次都写到末尾)。而且,把数据刷到磁盘上是不必须的。(如果PRAGMA synchronous是FULL,每次commit要刷一次,否则不刷。)
读操作的性能有所下降,因为需要从WAL文件中查找内容,花费的时间和WAL文件的大小有关。wal-index可以缩短这个时间,但是也不能完全避免。因此需要保证WAL文件的不会太大。
为了保护数据库不被损坏,需要在把WAL文件写入数据库之前把WAL文件刷入磁盘;在重置WAL文件之前要把数据库内容刷入数据库文件。此外checkpoint需要查找操作。这些因素使得checkpoint比写操作慢一些。
默认策略是很多线程可以增长WAL文件。把WAL文件大小变得比1000page大的那个线程要负责进行checkpoint。会导致绝大部分读写操作都是很快的,随机有一个写操作非常慢。也可以禁用自动checkpoint的策略,定期在一个线程或进程中进行checkpoint操作。
高效的写操作希望WAL文件越大越好;高效的读操作希望WAL文件越小越好。两者存在一个tradeoff。
激活和配置WAL模式
PRAGMA journal_mode=WAL;,如果成功,会返回"wal"。
自动checkpoint
可以手动checkpoint
sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb)
配置checkpoint
sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
Application-Initiated Checkpoints
可以在任意一个可以进行写操作的数据库连接中调用sqlite3_wal_checkpoint_v2()或sqlite3_wal_checkpoint()。
WAL模式的持久性
当一个进程设置了WAL模式,关闭这个进程,重新打开这个数据库,仍然是WAL模式。
如果在一个数据库连接中设置了WAL模式,那么这个数据库的所有连接都将被设为WAL模式。
只读数据库
如果数据库需要恢复,而你只有读权限,没有写权限,那么你不能读取这个数据库,因为进行读操作的第一步就是恢复数据库。
类似的,因为WAL模式下的数据库进行读操作时,需要类似数据库恢复的操作,因此如果只有读权限,也不能对打开数据库。
WAL的实现需要有一个基于WAL文件的哈希表在共享内存中。在Unix和Windows的VFS实现中,是基于MMap的。将共享内存映射到同目录下的"-shm"文件中。因此即使是对WAL模式下的数据库文件进行读操作,也需要写权限。
为了把数据库文件转化为只读的文件,需要先把这个数据库的日志模式改为"delete".
避免过大的WAL文件
WAL-index的共享内存实现
在WAL发布之前,曾经尝试过将wal-index映射到临时目录,如/dev/shm或/tmp。但是不同的用户看到的目录是不同的,所以此路不通。
后来尝试将wal-index映射到匿名的虚拟内存块中,但是无法在不用的Unix版本中保持一致。
最终决定采用将wal-index映射到同目录下。这样子会导致不必要的磁盘IO。但是问题不大,是因为wal-index很少超过32k,而且从不会调用sync操作。此外,最后一个数据库连接关闭以后,这个文件会被删除。
如果这个数据库只会被一个进程使用,那么可以使用heap memory而不是共享内存。
不用共享内存实现WAL
在3.7.4版本以后,只要SQLite的lock mode被设为EXCLUSIVE,那么即使共享内存不支持,也可以使用WAL模式。
换句话说,如果只有一个进程使用SQLite,那么不用共享内存也可以使用WAL。
此时,将lock mode改为normal是无效的,需要实现取消WAL模式。
参考链接:https://blog.csdn.net/tianxuhong/article/details/85763621