Small. Fast. Reliable.
Choose any three.
SQLite文件IO规范
目录
本文档的某些功能需要Javascript,包括目录,图形编号和内部参考(章节编号和超链接)。

概述

SQLite将整个数据库存储在一个文件中,其格式在SQLite文件数据库文件格式 文档ff_sqlitert_requirements中进行了描述。每个数据库文件都存储在文件系统中,该文件系统大概是由主机操作系统提供的。主机应用程序需要直接提供一个适配器组件来实现SQLite虚拟文件系统接口(在capi_sqlitert_requirements中进行了描述),而不是直接与操作系统连接。适配器组件负责将SQLite进行的调用转换为VFS接口调用对操作系统提供的文件系统接口的调用。图fig_vfs_role中描述了这种安排 。

数字 -虚拟文件系统(VFS)适配器

尽管设计使用VFS 接口读取和更新存储在文件系统中的数据库文件的内容的系统很容易,但是这种系统仍需要解决一些复杂的问题:

  1. 即使应用程序,操作系统或电源故障在更新数据库文件的过程中途或之后发生,SQLite仍需要实现原子性和持久性事务(ACID缩写中的“ A”和“ D”)。

    为了在潜在的应用程序,操作系统或电源故障的情况下实现原子事务,数据库编写者在写入数据库文件之前,将要修改的数据库文件的那些部分的副本写到第二个文件(日记文件)中。 。如果在修改数据库文件时确实发生了故障,SQLite可以基于日志文件的内容重建原始数据库(尝试进行修改之前)。

  2. 需要SQLite来实现隔离的事务(ACID缩写中的“ I”)。

    这是通过使用VFS适配器提供的文件锁定功能来序列化写入程序(写入事务)并防止写入程序进行更新的过程中读取程序(读取事务)访问数据库文件来完成的。

  3. 出于性能原因,将文件系统读取和写入的数据量减最少是有利的。

    正如人们所期望的那样,通过将数据库文件的某些部分缓存在主内存中,可以最大程度地减少从数据库文件读取的数据量。此外,属于同一写入事务的数据库文件的多个更新可以缓存在主内存中,然后一起写入文件,从而实现更有效的IO模式,并消除了如果数据库属于数据库可能发生的冗余写入操作。在单个写入事务中多次修改文件。

以上几点的系统要求参考。

本文档详细描述了SQLite使用VFS适配器组件提供的API来解决问题和实现上面列举的策略的方式。它还指定了有关VFS适配器可访问的系统属性的假设。例如,在fs_characteristics部分中介绍了有关在更新数据库文件时发生电源故障而可能发生的数据损坏程度的特定假设 。

本文档未指定由caps_sqlitert_requirements保留的VFS适配器组件必须实现的接口的详细信息 。

与其他文件的关系

与C-API要求有关:

  1. 打开连接。
  2. 关闭连接。

与SQL要求有关:

  1. 打开一个只读事务。
  2. 终止只读事务。
  3. 打开一个读写事务。
  4. 进行读写事务。
  5. 回滚读写事务。
  6. 打开对帐单交易。
  7. 提交对帐单交易。
  8. 回滚语句交易。
  9. 提交多文件事务。

与文件格式要求有关:

  1. 固定(读取)数据库页面。
  2. 取消固定数据库页面。
  3. 修改数据库页面的内容。
  4. 将新页面追加到数据库文件。
  5. 从数据库文件末尾截断页面。

文件结构

vfs_assumptions本文件的描述即将其VFS适配器组件提供接入系统所做的各种假设。介绍了VFS实现所需的基本功能和功能,以及capi_sqlitert_requirements中VFS接口的 说明vfs_assumptions节 通过更详细地描述对本文档中介绍的算法所依赖的VFS实现的假设进行补充。其中一些假设与性能问题有关,但大多数假设与在修改数据库文件的过程中发生故障后文件系统的预期状态有关。

database_connections 部分介绍了数据库连接的概念,它是文件句柄和用于访问数据库文件的内存中高速缓存的组合。它还描述了创建(打开)新数据库连接以及销毁(关闭)新数据库连接时所需的VFS操作。

reading_data 节描述了打开读取事务和从数据库文件读取数据所需的步骤。

Writing_data 部分描述了打开写事务并将数据写到数据库文件所需的步骤。

回滚 部分介绍了由于明确的用户指令或由于SQLite在更新数据库文件的过程中发生应用程序,操作系统或电源故障而导致中止的 写入事务回滚(还原)的方式。

page_cache_algorithms 节介绍了一些算法,这些算法可用来确定页面缓存真正缓存了数据库文件的哪些部分,以及它们对所需VFS操作的数量和性质的影响。首先,在本文档中包含页面缓存(这主要是实现细节)似乎有些奇怪。但是,有必要确认并描述页面缓存,以便对SQLite执行的IO的性质和数量提供更完整的说明。

词汇表

准备好本文档后,使词汇表保持一致,然后在此处添加词汇表。

VFS适配器相关假设

本节记录了有关VFS适配器可访问的系统的那些假设。fs_characteristics小节中提到的假设 特别重要。如果这些假设不成立,则电源或操作系统故障可能会导致SQLite数据库损坏。

与绩效相关的假设

SQLite使用本节中的假设来尝试加快读写数据库文件的速度。

假定按顺序将一系列顺序的数据块写入文件要比按任意顺序写入相同的块快。

系统故障相关假设

在发生操作系统或电源故障的情况下,可用的文件系统软件和存储硬件的各种组合可提供有关故障发生之前或发生期间写入文件系统的数据完整性的不同级别的保证。为了安全地修改数据库文件,SQLite需要执行的IO操作的确切组合取决于目标平台的确切特征。

本节描述在电源或系统故障后SQLite对文件系统内容所做的假设。换句话说,它描述了此类事件可能导致的文件和文件系统损坏的程度。

SQLite使用数据库文件文件句柄的xDeviceCharacteristics()和xSectorSize()方法查询实现的文件系统特征。仅在数据库文件上打开的文件句柄上才调用这两种方法。日志文件主日志文件临时数据库文件不调用它们 。

通过调用xSectorSize()方法确定 的文件系统扇区大小值是512到32768之间的2的幂,包括精确确定该大小的参考。SQLite假定基础存储设备将数据存储在每个扇区大小扇区大小的字节的块中。还假定每个文件的每个扇区大小字节的对齐块 存储在单个设备扇区中。如果文件的大小不是扇区大小 字节的精确倍数,则最终设备扇区部分为空。

通常,SQLite假定,如果在更新扇区的任何部分时发生电源故障,则在恢复后会怀疑整个设备扇区的内容。写入文件中扇区的任何部分后,假定修改后的扇区内容保存在系统内某个位置(主内存,磁盘高速缓存等)的易失性缓冲区中。SQLite直到更新的数据通过调用VFS xSync()方法成功同步了相应的文件后,才假定更新的数据已到达持久性存储介质。同步文件会导致对该文件的所有修改,直到该点被提交到持久性存储中。

基于以上内容,SQLite是围绕文件系统模型设计的,据此,写入文件的任何扇区都被视为处于过渡状态,直到成功同步文件为止 。如果扇区处于瞬态状态时发生电源或系统故障,则无法预测恢复后的内容。它可能被正确地写入,根本不被写入,被随机数据或其任何组合所覆盖。

例如,如果给定文件系统的扇区大小为2048字节,而SQLite打开一个文件并向该文件的偏移量3072写入1024字节的数据块,则根据模型,文件的第二个扇区为在过渡状态。如果在文件句柄下一次调用xSync()之前或期间发生电源故障或操作系统崩溃,则在系统恢复之后,SQLite会假定字节偏移量2048和4095(含)之间的所有文件数据均无效。还假定由于文件的第一个扇区(包含从字节偏移0到2047(含)在内的数据)是有效的,因为当崩溃发生时它不是处于过渡状态。

假设在电源或系统故障后瞬态的任何扇区都可能损坏,这是一种非常悲观的方法。一些现代系统提供了比这更复杂的保证。SQLite允许VFS实现在运行时指定当前平台支持零个或多个以下属性:

故障相关假设详细信息

本节描述了父节中提出的假设如何应用于VFS提供给SQLite的各个API函数和操作,以修改文件系统的内容。

SQLite使用以下四种类型的操作的组合来操作文件系统的内容:

此外,需要所有VFS实现来提供同步文件操作,该 操作可通过sqlite3_file对象的xSync()方法访问,该操作用于将文件的创建,写入和截断操作刷新到持久性存储介质。

本节中的形式化假设是指系统故障 事件。在这种情况下,这应解释为导致系统停止运行的任何故障。例如电源故障或操作系统崩溃。

在成功同步文件之前, SQLite不会假定创建文件操作实际上已经修改了持久性存储中的文件系统记录。

如果在“创建文件”操作期间或之后但在同步创建的文件之前发生系统故障,则SQLite会假定在系统恢复后,创建的文件可能不存在。

当然,在系统恢复后它也确实存在。

如果SQLite执行了“创建文件”操作,然后同步了创建的文件,则SQLite假定与“创建文件”操作相对应的文件系统修改已提交给持久性媒体。假定如果在成功同步文件后的任何时间发生系统故障 ,那么可以保证文件在系统恢复后出现在文件系统中。

删除文件操作(通过向VFS xDelete()方法的调用被调用)被假定为是一个原子和耐用操作。

如果“删除文件”操作(对VFS xDelete()方法的调用)成功返回后的任何时间发生系统故障,则假定在系统恢复后文件系统将不包含已删除的文件。

如果在“删除文件”操作期间发生系统故障,则假定在系统恢复后,文件系统将包含尝试删除操作前的状态下正在删除的文件,或者根本不包含该文件。假定不可能仅由于在“删除文件”操作期间发生故障而导致文件损坏。

直到相应文件同步之后,才认为截断文件操作 的效果不会持久 。

如果在“截断文件”操作期间或之后但在同步截断的文件之前发生系统故障,则SQLite会假定截断的文件大小等于或大于截断文件的大小。 。

如果在“截断文件”操作期间或之后但在同步已截断的文件之前发生系统故障,则假定文件的内容(不超过要截断的文件的大小)未损坏。

以上两个假设可以解释为,如果在文件截断之后但在同步截断的文件之前发生系统故障,则可能不信任要截断的文件之后的文件内容。它们可能包含原始文件数据,也可能包含垃圾。

如果SQLite执行了“截断文件”操作,然后将被截断的文件进行了同步,则SQLite假定与“截断文件”操作相对应的文件系统修改已提交给持久性媒体。假定如果在成功同步文件后的任何时间发生系统故障 ,则可以保证文件截断的影响会在恢复后出现在文件系统中。

写文件操作修改所述文件系统内的现有文件的内容。它还可能会增加文件的大小。假定在同步了相应的文件之后,才使写入文件操作的效果保持 不变

如果在“写入文件”操作期间或之后但在同步相应文件之前发生系统故障,则假定在系统恢复后,由写入文件操作跨越的所有扇区的内容 都是不可信任的。这包括写入文件操作未实际修改的扇区区域。

如果支持的系统上发生系统故障 原子写为的大小的块属性Ñ字节以下的对齐写入Ñ 字节到一个文件,但该文件已被成功地前同步,则假定以下恢复所有扇区跨越由写入操作已正确更新,或者根本没有任何扇区被修改。

如果在执行将数据追加到文件末尾而不修改任何现有文件内容但在成功同步文件之前将数据追加到文件末尾的写操作之后,在支持安全追加的系统上发生系统故障 ,则在恢复后假定为数据已正确添加到文件,或者文件大小保持不变。假定不可能扩展文件,但使用不正确的数据填充文件。

系统恢复后,如果按照A21008的定义将设备扇区视为不可信任,并且A21011或A21012都不适用于写入的字节范围,则无法假定恢复后的扇区内容。假设有可能正确地写入这样的扇区,而不是根本不写入,而是填充有垃圾数据或其任何组合。

如果在导致文件增长的“写入文件”操作期间或之后,但在同步相应文件之前发生系统故障,则假定恢复后文件的大小与恢复之前的文件大小相同或更大。最近同步的时间

如果系统支持顺序写入属性,则可以针对从系统故障中恢复后的文件系统状态做出进一步的假设。具体来说,假设创建,截断,删除和写入文件操作以与SQLite执行顺序相同的顺序应用于持久性表示形式。此外,假定文件系统等待,直到尝试将一项操作安全地写入持久性介质,然后再尝试执行另一项操作,就像相关文件在每次操作后同步一样

如果在支持顺序写入属性的系统上发生系统故障 ,则假定所有上次同步任何文件之前完成的操作 都已成功提交到持久性介质。

如果在支持顺序写入属性的系统上发生系统故障 ,则假定文件系统在恢复之后可能处于的一组可能状态与自从最近的一个文件的时间同步本身就是后面一个同步文件操作,并且可以在任何写或发生系统故障同步文件操作。

数据库连接

在本文档中,术语“数据库连接”的含义与可能假定的含义略有不同。sqlite3_open()sqlite3_open16() API(参考)返回的句柄称为数据库句柄。一个数据库连接是使用一个单一的文件句柄,这是保持打开的连接的生存期,以一个单一的数据库文件的连接。使用SQL ATTACH语法,可以通过单个数据库句柄访问多个 数据库连接。或者,使用SQLite的共享缓存模式功能,多个 数据库句柄可以访问单个数据库数据库连接

通常情况下,一个新的数据库连接,每当用户打开新的开放数据库句柄上一个真正的数据库文件(不是内存数据库),或者当一个数据库文件附加到现有的数据库连接使用SQL ATTACH语法。但是,如果启用了共享缓存模式功能,则可以通过现有的数据库连接来访问数据库文件。有关共享缓存模式的更多信息 ,请参考Reference。本文档的open_new_connection部分详细介绍了打开新连接所需的各种IO操作。

同样,当用户关闭在真实数据库文件上打开的数据库句柄或使用ATTACH机制将一个或多个真实数据库文件附加到该数据库句柄时,或者当从数据库中分离真实数据库文件时,通常会关闭数据库连接。使用DETACH语法的数据库连接。同样,例外是如果 启用了共享缓存模式。在这种情况下,直到用户数达到零,数据库连接才会关闭。关闭数据库连接中需要执行的与IO相关的步骤,请参见shutdown_database_connection部分。

在完成第4和第5节之后,回到这里,看看是否可以添加与每个数据库连接关联的状态项列表,以使事情更容易理解。也就是说,每个数据库连接都有一个文件句柄,页面缓存中的一组条目,预期的页面大小等。

打开一个新的连接

本节描述了在创建新的数据库连接时发生的VFS操作。

打开一个新的数据库连接是一个两步过程:

  1. 在数据库文件上打开一个文件句柄。
  2. 如果步骤1成功,则尝试使用新的文件句柄从数据库文件中读取 数据库文件头

在上述过程的步骤2中,在读取数据库文件之前未将其锁定。这是read_data部分中描述的锁定规则的唯一例外。

尝试读取数据库文件头的原因 是要确定数据库文件使用的页面大小。因为无法确定页面大小 而没有至少对数据库文件持有共享锁(因为自从读取数据库文件头以来,某些其他数据库连接可能已更改它),所以从数据库读取的值 文件标头被称为预期页面大小

当需要新的数据库连接时,SQLite应尝试在数据库文件上打开文件句柄。如果尝试失败,则不会创建新的数据库连接,并且会返回错误。

当需要新的数据库连接时,在打开新的文件句柄之后,SQLite将尝试读取数据库文件的前100个字节。如果尝试由于打开的文件小于100字节以外的任何其他原因而失败,则文件句柄将关闭,不会创建新的数据库连接,而是返回错误。

如果从新打开的数据库文件中成功读取了数据库文件头,则应将预期的连接页面大小设置为存储在数据库头的页面大小字段中的值。

如果无法从新打开的数据库文件中读取数据库文件头(因为该文件的大小小于100个字节),则应将预期的连接页面大小设置为SQLITE_DEFAULT_PAGESIZE选项的编译时间值。

断开连接

本节介绍关闭(销毁)现有数据库连接时发生的VFS操作。

关闭数据库连接很简单。关闭打开的VFS文件句柄,并释放与内存中页面高速缓存相关的资源。

关闭数据库连接后,SQLite应在VFS级别关闭关联的文件句柄。

数据库连接关闭时,所有相关的页面缓存条目都将被丢弃。

页面缓存

SQLite数据库文件的内容被格式化为一组固定大小的页面。有关所用格式的完整说明,请参见ff_sqlitert_requirements。用于特定数据库的页面大小作为数据库文件标头的一部分存储在文件前100个字节内的众所周知的偏移量处。SQLite对数据库文件执行的几乎所有读取和写入操作都是在数据页大小字节大小的块上完成的。

在单个进程中运行的所有SQLite数据库连接共享一个页面缓存。该页面缓存缓存数据从主内存中的数据库文件在每个页面的基础上读取。当SQLite需要来自数据库文件的数据来满足数据库查询时,它会在从数据库文件加载数据之前,先检查 页面缓存中是否有所需数据库页面的可用缓存版本。如果找不到可用的缓存条目,并且从数据库文件中加载了数据库页面数据,则将其缓存在页面缓存中如果以后再次需要相同的数据。因为假定从数据库文件读取比从主内存读取慢一个数量级,所以在页面缓存中缓存数据库页面内容以最大程度地减少对数据库文件执行的读取操作的次数是显着的性能增强。

页面缓存也被用来缓冲数据库的写操作。当需要SQLite修改 组成数据库文件的一个或多个数据库页面时,它首先会修改页面缓存中页面的缓存版本。那时该页面被认为是“脏”页面。稍后,“脏”页面的新内容将通过VFS接口从页面缓存复制到数据库文件中。在页面缓存中缓冲写操作可以减少数据库文件所需的写操作次数(如果同一页面被更新两次),并且可以基于fs_performance部分中概述的假设进行优化

数据库读取和写入操作,并以何种方式它们相互作用与和使用页面缓存,进行了详细的章节中描述 reading_datawriting_data本文件的分别。

在任何时候,页面高速缓存都包含零个或多个页面高速缓存条目,每个条目都具有与之关联的以下数据:

上面列表中的前两个元素,即关联的数据库连接页面号,唯一地标识了 页面缓存条目。在任何时候,可以在页面缓存包含两个条目其中两个数据库连接页面数量是相同的。或者,换句话说,单个数据库连接页面缓存中永远不会缓存数据库页面的一个以上副本。

在任何时候,根据以下定义,每个页面缓存条目都可以称为干净页不可写脏页可写脏页

page_cache_algorithms节中提供了 用于确定具有已修改内容的页面缓存条目脏页还是可写页的确切逻辑。

因为主内存是有限的资源,所以不能允许页面缓存无限期地增长。结果,除非进程中数据库连接打开的所有数据库文件都很小,否则有时必须从页面缓存中丢弃数据。实际上,这意味着必须清除页面缓存条目以为新条目腾出空间。如果一个页面缓存条目正在从删除页面缓存到无主内存是一个肮脏的页面,然后将其内容必须被保存到数据库文件之前,它可以在不丢失数据被丢弃。以下两个小节描述了页面缓存使用的算法确定确切时间何时清除(丢弃)现有的页面缓存条目

页面缓存配置

描述设置用于配置页面缓存限制的参数。

页面缓存算法

描述配置参数使用方式的要求。关于LRU等

读取数据

为了将数据从数据库返回给用户,例如作为SELECT查询的结果,SQLite必须在某个时候从数据库文件中读取数据。通常,从数据库文件中以页面大小字节的对齐块读取数据。例外是在检查数据库文件头字段时,才可以知道数据库使用的 页面大小

除了两个例外,数据库连接必须在数据库上具有打开的事务(只读事务读/写事务),然后才能从数据库文件中读取数据。

这两个例外是:

打开事务后,从数据库连接读取数据是一个简单的操作。使用在数据库文件上打开的文件句柄的xRead()方法,一次读取一个所需的数据库文件页面。SQLite从不读取部分页面,并且始终对每个所需页面使用一次对xRead()的调用。

读取数据库页面的数据后,SQLite将数据的原始页面存储在页面缓存中。上层每次需要页面数据时,都会查询页面高速缓存以查看其是否包含当前数据库连接存储的所需页面的副本。如果可以找到这样的条目,则从页面缓存而不是数据库文件中读取所需的数据。只有与数据库上的开放式事务处理事务(只读事务处理读/写事务处理)的连接才可以从页面缓存中读取数据 。从这个意义上说,从页面缓存中读取与从数据库文件读取没有什么不同。

请参阅page_cache_algorithms部分,以获取关于页面数据在页面缓存中的存储方式以及存储时间的 描述

除了H35070要求的读取操作以及作为打开只读事务的一部分进行的读取之外,当从数据库文件中读取任何数据时,SQLite都应确保数据库连接具有开放的只读或读/写事务。

除了H35070和H21XXX描述的那些读取操作外,SQLite还应按页面大小字节的对齐块从数据库文件读取数据 ,其中page-size是数据库文件使用的数据库页面大小。

在使用存储在页面缓存中的数据满足用户查询之前 ,SQLite必须确保数据库连接具有开放的只读或读/写事务。

开一个只读交易

在可以从数据库文件读取数据或从页面缓存查询数据之前,必须通过关联的数据库连接成功打开只读事务(即使该连接最终将作为读/写操作写入数据库,也是如此) 交易仅可通过升级从打开 只读事务)。本节介绍打开只读事务的过程

只读事务 的关键元素是在数据库文件上打开的文件句柄获取并持有 数据库文件上的共享锁。因为一个连接在实际修改数据库文件的内容之前需要一个独占锁,并且根据定义,当一个连接持有一个共享锁时,其他任何连接都不能持有一个 独占锁,持有一个共享锁可以保证没有其他进程可能会在只读事务保持打开状态时修改数据库文件。这样可以确保只读事务与其他数据库用户的事务充分隔离(请参阅概述)。

获取数据库文件上的共享锁本身非常简单,SQLite只是调用数据库文件句柄的xLock()方法。作为打开只读事务的一部分而发生的其他一些过程非常复杂。SQLite打开只读事务所需采取的步骤(按照它们必须发生的顺序)如下:

  1. 共享锁数据库文件被获得。
  2. 该连接检查文件系统中是否存在热日志文件。如果是这样,则将其回滚再继续。
  3. 连接检查页面缓存中的数据是否仍然可以信任。如果不是,则所有页面缓存数据都将被丢弃。
  4. 如果文件大小不为零字节,并且页面高速缓存不包含数据库第一页的有效数据,则必须从数据库中读取第一页的数据。

当然,尝试上面列出的4个步骤中的任何一个都可能会发生错误。如果发生这种情况,那么将释放共享锁(如果已获得共享锁),并且将错误返回给用户。以上过程的第2步在hot_journal_detection部分中进行了详细描述 。cache_validation部分 描述了上述步骤3所标识的过程。在步骤read_page_one中可以找到有关步骤4的更多详细信息。

当需要使用 数据库连接打开只读事务时,SQLite应首先尝试在数据库文件上打开的文件句柄上获取共享锁

如果在打开只读事务时,SQLite无法获取数据库文件上的共享锁,则该过程将被放弃,不打开任何事务,并且将错误返回给用户。

尝试获取共享锁可能失败的最常见原因是某些其他连接持有排他挂起锁。但是,它也可能会失败,因为在xLock()方法的调用内发生了一些其他错误(例如,与IO或与通信相关的错误)。

在打开只读事务时,在成功获取数据库文件的共享锁之后,SQLite应尝试检测并回滚与同一数据库文件关联的热日志文件。

如果在打开只读事务时,SQLite在尝试检测或回滚热日志文件时遇到错误,那么将释放数据库文件上的共享锁,不打开任何事务,并且将错误返回给用户。

hot_journal_detection 节包含对上述要求中引用的热新闻文件的检测的描述和要求。

假设未发生任何错误,则在尝试检测并回滚热日志文件之后,如果页面缓存包含与当前数据库连接关联的任何条目,则SQLite应通过测试文件更改计数器来验证页面缓存的内容。此过程称为 缓存验证

缓存验证处理的详细内容部分中描述 cache_validation

如果需要通过H35040规定的缓存验证程序,并不能证明该页面缓存使用当前的相关条目的数据库连接是有效的,那么SQLite的应放弃与当前相关联的所有条目的数据库连接页面缓存

上面的编号列表指出,数据库文件首页的数据(如果存在)并且尚未加载到页面缓存中,则必须先从数据库文件中读取该数据,然后才能将其视为只读事务。这由需求H35240处理。

热点期刊检测

本节介绍SQLite用于检测 热日志文件的过程。如果检测到热日志文件,则表明在某个时候将事务写入数据库的过程已中断,并且需要进行恢复操作(热日志回滚)。本节未描述的过程热轴颈回滚(见 hot_journal_rollback)或通过其的过程 热的日志文件可以被创建(见 writing_data)。

用于检测热门新闻文件的过程非常复杂。发生以下步骤:

  1. 使用VFS xAccess()方法,SQLite查询文件系统以查看是否存在与数据库关联的日志文件。如果没有,则没有热新闻文件。
  2. 通过调用在数据库文件上打开的文件句柄的xCheckReservedLock()方法,SQLite检查其他某个连接是否持有保留的锁或更大的。如果某个其他连接确实保留了保留锁,则表明该其他连接正在进行读/写事务(请参见Writing_data部分)。在这种情况下, 日记文件不是热门新闻,因此不能回滚。
  3. 使用在数据库文件上打开的文件句柄的xFileSize()方法,SQLite检查数据库文件的大小是否为0字节。如果是这样,则日记文件不被视为热日记文件。在这种情况下,无需回滚日志文件,而是通过调用VFS xDelete()方法将其从文件系统中删除。从技术上讲,这里存在比赛条件。持有排他锁后,应将这一步移至。
  4. 试图升级到数据库文件的排他锁。如果尝试失败,则将删除所有锁,包括最近获得的共享锁。尝试打开只读事务失败。当其他某个连接也尝试打开只读事务并且由于另一个连接也持有共享锁而获得排他锁的尝试 失败时,就会发生这种情况 。它留给另一个连接来回滚热日记帐
    在这种情况下, 将文件句柄锁直接从共享锁升级为互斥锁非常重要,而不是在获取要写入数据库文件的互斥锁时(需要在首先写入到保留锁或挂起锁)(请参见sectioning_data)。如果在这种情况下SQLite首先升级到保留锁或 挂起的锁,那么第二个尝试在数据库文件上打开读取事务的进程可能会检测到保留的锁定此过程的第2步,得出没有热日志的结论,然后开始从数据库文件读取数据。
  5. 再次调用xAccess()方法以检测日志文件是否仍在文件系统中。如果是,则它是一个热门新闻文件,SQLite会尝试将其回滚(请参阅rollback一节 )。

主日记文件指针?

以下要求更详细地描述了上述过程的步骤1。

当需要尝试检测热日志文件时,SQLite应首先使用VFS层的xAccess()方法检查文件系统中是否存在日志文件。

如果H35140要求的对xAccess()的调用失败(由于IO错误或类似原因),则SQLite将放弃尝试打开一个只读事务,放弃数据库文件上持有的共享锁,并向该错误返回一个错误。用户。

当需要尝试检测热日志文件时,如果H35140要求的对xAccess()的调用指示不存在日志文件,则SQLite应当得出结论,文件系统中没有热日志文件,因此,无需热日志回滚

以下要求更详细地描述了上述过程的步骤2。

当需要尝试检测热日志文件时,如果H35140要求的对xAccess()的调用指示存在日志文件,则将调用数据库文件file-handle的xCheckReservedLock()方法来确定是否其他进程正在对数据库文件持有保留或更大的锁定。

如果H35160要求对xCheckReservedLock()的调用失败(由于IO或其他内部VFS错误),则SQLite将放弃尝试打开只读事务,放弃 数据库文件上持有的共享锁并返回错误的尝试。给用户。

如果H35160要求对xCheckReservedLock()的调用指示其他某个数据库连接正在 对该数据库文件持有保留的或更大的锁,则SQLite将得出结论,认为没有热日志文件。在这种情况下,检测热日志文件的尝试结束。

以下要求更详细地描述了上述过程的步骤3。

如果在尝试检测热日志文件时,对xCheckReservedLock()的调用指示没有进程 对数据库文件持有保留的或更大的锁,则SQLite应使用VFS xOpen()在潜在的热日志文件上打开文件句柄。方法。

如果H35440要求的xOpen()调用失败(由于IO或其他内部VFS错误),则SQLite将放弃尝试打开只读事务,放弃数据库文件上持有的共享锁并返回错误的尝试。 给用户。

成功打开可能很热的日记文件上的文件句柄后,SQLite应使用打开文件句柄的xFileSize()方法查询文件的大小(以字节为单位)。

如果H35450要求的对xFileSize()的调用失败(由于IO或其他内部VFS错误),则SQLite将放弃尝试打开只读事务的操作,放弃数据库文件上持有的共享锁,关闭文件日志文件上打开的句柄,并向用户返回错误。

如果通过H35450要求的查询显示潜在热日志文件的大小为零字节,则SQLite应关闭对日志文件打开的文件句柄,并使用对VFS xDelete()方法的调用删除日志文件。在这种情况下,SQLite会得出结论,认为没有热日志文件

如果H35450要求的对xDelete()的调用失败(由于IO或其他内部VFS错误),则SQLite将放弃尝试打开只读事务,放弃数据库文件上持有的共享锁并返回错误的尝试。给用户。

以下要求更详细地描述了上述过程的步骤4。

如果通过H35450要求的查询显示潜在热日志文件的大小大于零字节,则SQLite应尝试 将数据库文件上的数据库连接所持有的共享锁直接升级为排他锁

如果由于任何原因尝试升级到H35470规定的排他锁均失败,则SQLite将释放数据库连接持有的所有锁,并关闭在日记文件上打开的文件句柄 。尝试打开只读事务 将被视为失败,并且将错误返回给用户。

最后,以下要求将更详细地描述上述过程的步骤5。

如果作为热日志文件检测过程的一部分,尝试成功升级到H35470要求的排他锁,则SQLite应使用VFS实现的xAccess()方法查询文件系统,以测试是否已日志文件仍存在于文件系统中。

如果H35490要求的xAccess()调用失败(由于IO或其他内部VFS错误),则SQLite将放弃尝试打开只读事务的操作,放弃数据库文件上持有的锁,关闭文件句柄在日志文件上打开并向用户返回错误。

如果H35490要求的对xAccess()的调用显示日志文件不再存在于文件系统中,则SQLite将放弃尝试打开只读事务的尝试,放弃对数据库文件持有的锁定,关闭文件日志文件上打开的句柄,并向用户返回SQLITE_BUSY错误。

如果H35490要求的xAccess()查询显示该日志文件仍存在于文件系统中,则SQLite将得出以下结论:该日志文件是需要回滚的热日志文件。SQLite应立即开始热日志回滚

缓存验证

数据库连接打开读取事务时页面缓存可能已经包含与数据库连接关联的 数据。但是,如果自从加载高速缓存的页面以来另一个进程已修改数据库文件,则高速缓存的数据可能无效。

SQLite使用文件更改计数器数据库文件头中的字段)确定属于数据库连接页面缓存条目是否有效。所述 文件的改变计数器是存储在起始字节4字节大端整数字段偏移的24数据库文件头。在完成以任何方式修改数据库文件内容的读/写事务之前(请参见Writing_data部分),存储在文件更改计数器中的值将递增。当一个数据库连接解锁数据库文件,它存储文件更改计数器的当前值。稍后,在打开新的只读事务时,SQLite将检查存储在数据库文件中的文件更改计数器的值。如果自解锁数据库文件以来该值未更改,则 可以信任页面缓存条目。如果该值已更改,则无法信任页面缓存条目,并且与当前数据库连接关联的所有条目都将被丢弃。

当在数据库文件上打开的文件句柄被解锁时,如果 页面缓存包含一个或多个属于相关数据库连接的条目,则SQLite将在内部存储文件更改计数器的值。

当需要在打开读取事务时执行缓存验证时,SQLite应使用数据库连接文件句柄的xRead()方法从数据库文件的字节偏移量24开始读取16字节的块。

为什么要使用16个字节的块?为什么不4?(与加密数据库有关)。

在执行缓存验证时,按照H35190的要求加载16个字节的块之后,SQLite必须将存储在该块的前4个字节中的32位big-endian整数与文件更改计数器的最新存储值进行比较(请参阅H35180) )。如果值不相同,则SQLite将得出缓存内容无效的结论。

要求H35050(第open_read_only_trans节)指定了SQLite在确定缓存内容无效时需要执行的操作。

第1页和预期的页面大小

作为在大于0字节的数据库文件上打开读取事务的最后一步,需要SQLite将数据库页面1的数据加载到页面缓存中(如果尚不存在)。这比看起来要稍微复杂一些,因为此时数据库页大小尚不清楚。

即使不能确定数据库的页面大小,SQLite通常也可以通过假定它等于连接预期的页面大小来正确猜测。该预期的页面大小 是价值页面大小字段中,从读取 数据库文件头,同时打开数据库连接(见open_new_connection),或在页面大小 的数据库文件时最为读事务得出的结论。

读取事务结束期间,在解锁数据库文件之前,SQLite应将预期的连接 页面大小设置为当前数据库的页面大小

作为打开新读取事务的一部分,在执行高速缓存验证之后,如果页面高速缓存中没有数据库页面1的数据,SQLite应使用连接的xRead()方法从数据库文件的开头读取N个字节。文件句柄,其中N是连接当前的 预期页面大小值。

如果按H35230的要求读取了第1页数据,则数据库文件头中出现的消耗读取块的前100个字节的页面大小字段的值 与连接当前预期的页面大小不同,则在 预期的页面大小设置为这个值,数据库文件被解锁并打开整个程序读取事务 被重复。

如果按H35230的要求读取了第1页数据,则出现在数据库文件头中且消耗读取块的前100个字节的页面大小字段的值 与连接当前预期的页面大小相同,然后读取的数据块作为页面1存储在页面高速缓存中。

读取数据库数据

添加一些有关首先检查页面缓存的信息,等等。

结束只读事务

若要结束只读事务,SQLite只需放弃 在数据库文件上打开的文件句柄上的共享锁。无需其他操作。

当需要结束只读事务时,SQLite将通过调用文件句柄的xUnlock()方法来释放数据库文件上持有的共享锁

另请参见以上要求H35180和H35210。

写数据

使用DDL或DML SQL语句,SQLite用户可以修改数据库文件的内容和大小。ff_sqlitert_requirements确切描述了如何将逻辑数据库的更改转换为对数据库文件的 更改。从本文档中描述的子系统的角度来看,每个执行的DDL或DML语句都会导致零个或多个数据库文件页面的内容被新数据覆盖。DDL或DML语句还可以在数据库文件的末尾添加或截断一个或多个页面。一个或多个DDL和/或DML语句组合在一起组成一个写事务。一个写事务必须具有章节概述中所述的特殊属性; 一个写事务必须隔离,耐用和原子。

SQLite使用以下技术来实现这些目标:

页面缓存使用它们写入数据前要缓冲的修改数据库文件的图像数据库文件。当作为写事务内操作的结果需要修改页面的内容时,修改后的副本将存储在页面高速缓存中。同样,如果将新页面附加到数据库文件的末尾,则将它们添加到页面缓存中, 而不是立即将其写入文件系统内的数据库文件中。

理想情况下,整个写事务的所有更改都将缓冲在页面缓存中,直到事务结束为止。当用户提交事务时,考虑到fs_performance部分中列举的假设,所有更改都将以最有效的方式应用于数据库文件。不幸的是,由于主内存是有限的资源,因此对于大型事务并非总是可能的。在这种情况下,更改将在页面缓存中进行缓冲,直到达到某些内部条件或限制,然后将其写出到数据库文件中,以便根据需要释放资源。第page_cache_algorithms 描述了在什么情况下将更改刷新到事务处理过程中以释放页面高速缓存资源的情况。

即使在写事务进行过程中未发生应用程序或系统故障,也可能 需要回滚操作,以将数据库文件和页面高速缓存恢复到事务开始之前的状态。如果用户显式请求事务回滚(通过发出“ ROLLBACK”命令),或者由于遇到SQL约束而自动请求回滚(请参见sql_sqlitert_requirements),则可能会发生这种情况 。因此,在页面甚至在页面缓存中修改页面之前,原始页面内容就存储在日记文件中

介绍以下小节。

日记文件格式

本节介绍了SQLite日志文件使用的格式。

日记文件由一个或多个日记标题,零个或多个日记记录以及可选的主日记指针组成。每个日记文件始终以日记头开头 ,后跟零个或多个日记记录。紧随其后的是第二个日记标题,然后是第二组零个或多个日记记录,依此类推。日记文件可以包含的日记标题的数量没有限制。在日记帐标题及其随附的日记帐记录集之后可以是可选的主日记指针。或者,该文件可能只是在最终日记记录之后结束。

本节仅描述日记文件的格式以及组成该文件的各种对象。但是,由于从系统故障恢复后,SQLite进程可能会读取日志文件(热日志回滚,请参见hot_journal_rollback部分 ),因此使用以下命令组合描述在文件系统内创建和填充文件的方式也很重要。写入文件同步文件截断文件操作。这些在write_transactions部分中进行了描述 。

日记标题格式

轴颈头扇区尺寸的大小,其中,字节的 扇区大小是通过打开上的数据库文件的文件句柄的xSectorSize方法返回的值。仅使用日志头的前28个字节,其余部分可能包含垃圾数据。每个日志标题的前28个字节由一个设置为已知值的8字节块组成,后跟5个大端32位无符号整数字段。

数字 -日记标题格式

fig_journal_header以图形方式描绘了日记帐标题的布局。下表描述了各个字段。该表的“字节偏移量”列中的偏移量相对于日记帐标题的开头。

字节偏移大小(以字节为单位)描述
08杂志的魔法领域始终包含用于识别SQLite的日志文件知名的8个字节的字符串值。字节值的众所周知的顺序是:
0xd9 0xd5 0x05 0xf9 0x20 0xa1 0x63 0xd7
84该字段(记录计数)设置为在日记文件中跟随此 日记标题日记记录数。
124初始化校验字段设置为一个伪随机值。它用作算法的一部分,用于计算此日记帐标题之后的所有日记帐记录的校验和。
164此字段(数据库页数)设置为在应用 与写事务相关联的任何修改之前数据库文件包含 的页数。
204此字段(扇区大小)设置为在其创建日志文件的设备的 扇区大小( 以字节为单位)。读取日志文件以确定每个日志标题的大小时,此值是必需的。
244页面大小字段包含使用相应的数据库页面大小的数据库文件 中,当日志文件被创建,以字节为单位。

所有日记帐标题均位于文件中,以便它们以扇区大小对齐的偏移量开始。为了实现此目的,可以在第二个和后续日记帐标题的开头 与与先前标题相关联的日记记录的结尾之间保留未使用的空间 。

日记记录格式

每个日志记录都包含由写事务修改的数据库页面的原始数据。如果需要回滚,则可以使用此数据将数据库页的内容还原到开始写入事务之前的状态。

数字 -日记记录格式

如下图所示,由图形Figure_journal_record图形化描述 的日记记录包含三个字段。字节偏移量相对于日记记录的开始 。

字节偏移大小(以字节为单位)描述
04与此日记记录关联的数据库页面的页码,存储为4字节的big-endian无符号整数。
4页面大小 该字段包含页面的原始数据,与写入事务开始之前在数据库文件中显示的原始数据完全相同 。
4 +页面大小4 该字段包含一个校验和值,该值是根据日记数据库页面数据的内容(前一个字段)和存储在前一个 日记头标题校验和初始值设定项字段中的值计算得出的 。

该组的日志记录遵循一个日记头日志文件被紧紧地包装在一起。对日记帐记录没有对齐要求,对 日记帐标题没有对齐要求。

硕士期刊指针

为了支持修改多个数据库文件的原子事务,SQLite有时会 在日志文件中包含主日志指针记录。在multifile_transactions部分中描述了多个文件事务。甲 主轴颈指针包含一个的名称的主日志文件 与一个校验和和一些公知的值,其允许沿着主轴颈指针时被识别为这样的日志文件回滚操作(部分期间被读 回退)。

日记帐标题一样主日记帐指针的起点始终位于扇区大小 对齐的偏移量处。如果 紧接在主日记帐指针之前出现的日记帐记录日记帐标题没有以对齐的偏移量结尾,则在日记帐记录日记帐标题的末尾与主日记帐指针的开始之间会保留未使用的空间。

数字 -主日记指针格式

主日志的指针,由图中图形描绘 figure_master_journal_ptr,包含五个字段,如下表中所述。字节偏移量相对于主日记帐指针的起始位置。

字节偏移大小(以字节为单位)描述
04此字段(锁定页号)始终设置为数据库锁定页的页号,该数据库锁定页 存储为4字节的big-endian整数。在锁定页面 是开始于字节的页偏移230数据库文件。即使数据库文件足够大以包含锁定页面,该锁定页面也永远不会用于存储任何数据,因此有效日志记录的前四个字节将永远不会包含该值。有关锁定页面的进一步说明,请参考 ff_sqlitert_requirements
4名称长度主期刊名称字段包含主日志文件的名称,编码成UTF-8字符串。字符串中没有附加nul-terminator。
4 +姓名长度4名称长度字段包含在字节上一字段的长度,格式为4字节大端无符号整数。
8 +姓名长度4校验字段包含存储为4字节大端符号整数的校验和值。校验和值计算为构成 主日记帐名称字段的字节之和,并将每个字节解释为8位带符号整数。
12 +姓名长度8 最后,日志魔术字段始终包含一个众所周知的8字节字符串值;与日记头的前8个字节中存储的值相同。众所周知的字节序列为:
0xd9 0xd5 0x05 0xf9 0x20 0xa1 0x63 0xd7

写交易

本节描述了SQLite写事务的进程。从本文档中描述的系统的角度来看,大多数写事务包括三个步骤:

  1. 写事务被打开。在opening_a_write_transaction部分中描述了此过程。

  2. 最终用户执行DML或DDL SQL语句,这些语句要求修改数据库文件的数据库文件的结构。这些修改可以是以下操作的任意组合:

    这些操作详细描述于部分所述 modifying_appending_truncatingff_sqlitert_requirements中描述了如何将用户DDL或DML SQL语句映射到这三个操作的组合。
  3. 写事务的结论和所做的更改永久提交到数据库。提交事务所需的过程在committing_a_transaction部分中进行了描述 。

作为上述步骤3的替代方法,可以回滚事务。事务回滚在部分回滚中进行了描述。最后,同样重要的是要记住,写入事务 可能会在任何时候被系统故障中断。在这种情况下,文件系统的内容(数据库文件日志文件)必须处于这种状态,以使数据库文件能够恢复到开始中断写入事务之前的状态。这称为热日志回滚,在本节中进行了描述。 hot_journal_rollbackfs_assumption_details节 描述了有关恢复后系统故障对文件系统内容的影响所做的假设。

开始写交易

页面缓存内修改任何数据库页面之前,数据库连接必须打开写事务。打开写事务要求数据库连接数据库文件上获得保留的锁(或更大的) 。因为获取数据库文件上的保留锁可以保证没有其他数据库连接可以保留或获取保留锁或更高,因此,没有其他数据库连接可以打开写交易

一个保留锁数据库文件可以被认为是对的排他锁的日志文件。没有 数据库连接可以读取或写入日志文件没有保留在相应或更高锁定 数据库文件

在打开写事务之前,数据库连接 必须具有打开的读事务,该事务通过open_read_only_trans节中描述的过程打开。这样可以确保不存在需要回滚的热新闻文件,并且可以信任存储在页面缓存中的所有数据。

一旦读事务已经打开,升级到 写事务是两个步骤,如下所示:

  1. 保留锁上获得的数据库文件
  2. 日志文件被打开,并在必要时(使用VFS XOPEN方法)创建和日志文件头使用的文件句柄xWrite方法的单一调用写入它的开始。

详细描述上述过程的步骤1的要求:

当需要打开数据库上的写事务时,如果所讨论的数据库连接尚未打开,则SQLite应首先打开一个读事务

当需要在数据库上打开写事务时,在确保已打开读事务之后,SQLite应通过调用在数据库文件上打开的文件句柄的xLock方法来获得对数据库文件的保留锁

如果获取要求H35360规定的保留锁的尝试失败,则SQLite应将尝试打开写入事务的尝试视为失败并将错误返回给用户。

详细描述上述过程的步骤2的要求:

当需要在数据库上打开写事务时,在获得数据库文件的保留锁之后,SQLite应在相应的日记文件上打开读/写文件句柄。

当需要在数据库上打开写事务时,在打开日记文件上的文件句柄之后,SQLite应将日记头附加到(当前为空)日记文件

编写日记标题

描述日记头如何附加到日记文件的要求:

当需要在日志文件上附加日志头时,SQLite可以通过 使用一次调用在日志文件上打开的文件句柄的xWrite方法来写入一个扇区大小的字节块来实现。写入的数据块应在日志文件当前末尾处或之后的最小扇区大小对齐偏移处开始。

H35680要求写入 的日志头的前8个字节应包含以下值,从字节偏移0到7依次为:0xd9、0xd5、0x05、0xf9、0x20、0xa1、0x63和0xd7。

H35680要求写入 的日记帐标题的字节8-11应当包含0x00。

H35680要求写入 的日记标题的第12-15字节应包含当前写入事务开始时数据库文件所包含的页数,格式为4字节的big-endian无符号整数。

H35680要求写入 的日志头的字节16-19应当包含伪随机生成的值。

H35680要求写入 的日志头的字节20-23应包含VFS层使用的扇区大小,格式为4字节的big-endian无符号整数。

H35680要求写入 的日志头的字节24-27应当包含数据库在写事务开始时使用的页面大小,格式为4字节的big-endian无符号整数。

修改,添加或截断数据库页面

当最终用户执行DML或DDL SQL语句来修改数据库架构或内容时,需要SQLite更新数据库文件映像以反映新的数据库状态。这涉及到修改,附加或截断多个数据库文件页面之一的内容。而不是直接使用VFS接口直接修改数据库文件,而是将更改首先存储在页面缓存中

在修改可能需要通过回滚操作还原的页面缓存中的数据库页面之前,必须将页面记录为 日记对页面进行日志记录是将页面原始数据复制到日志文件中的过程,以便在回滚写入事务时可以将其恢复。轴颈网页的过程中部分中描述 journalling_a_page

当在打开写事务时需要修改现有数据库页面的内容并且不是自由列表叶页面时,如果当前页面尚未在当前写事务中记录日志,则SQLite将记录该页面。

当需要修改现有数据库页面的内容时,SQLite将更新数据库页面内容的缓存版本,该版本存储为与页面关联的页面缓存条目的一部分。

将新的数据库页面附加到数据库文件后,无需将记录添加到日记文件。如果需要回滚,则基于存储在日志文件字节偏移量12处的值,数据库文件将被简单地截断为其原始大小。

当需要将新的数据库页面追加到数据库文件时,SQLite应创建一个与该新页面相对应的新页面缓存条目,并将其插入到页面缓存中。新页面缓存条目脏标志将被设置。

如果需要从数据库文件的末尾截断数据库页面,则将丢弃关联的页面高速缓存条目。调整后的数据库文件大小存储在内部。在提交当前写事务之前,实际上不会截断数据库文件 (请参阅committing_a_transaction部分)。

当从数据库文件的末尾打开写事务时,当需要截断(删除)一个已经存在且不是自由列表叶页面的数据库页面时,如果当前页面中尚未对该页面进行日志记录,则SQLite将记录该页面写交易

当需要从数据库文件的末尾截断数据库页面时,SQLite应从页面缓存中丢弃关联的页面缓存条目

记录数据库页面

通过将日志记录添加到 日志文件中来对页面进行日志记录日记记录的格式在journal_record_format部分中描述。

当需要期刊数据库页,SQLite的应首先附加页码页面被轴颈的 日志文件,格式为4字节大端无符号整数,使用单个调用文件句柄的xWrite方法在日志文件上打开。

当要求对数据库页进行日记记录时,如果尝试将页码附加到日记文件中成功,则应使用一次xWrite方法的调用将当前页数据(页面大小字节)附加到日记文件中。在日志文件上打开的文件句柄的大小。

当要求对数据库页面进行日志记录时,如果尝试将当前页面数据附加到日志文件成功,则SQLite应当使用一次调用,将4字节的大端整数校验和值附加到日志文件。在日志文件上打开的文件句柄的xWrite方法。

页面数据紧随其后 写入日志文件的校验和值(要求H35290)是页面数据和存储在日志标题中校验和初始值设定项字段 的函数(请参阅journal_header_format部分)。具体来说,它是校验和初始化程序与页面数据的第200个字节的值之和,该值被解释为8位无符号整数,从页面数据的第(page-size%200)个字节开始。例如,如果页面大小是1024个字节,则通过将偏移量23、223、423、623、823和1023(页面的最后一个字节)处的字节值与校验和初始值设定项的值相加来计算校验和

H35290要求的写入操作将写入日记文件 的校验和值等于存储在日记头(H35700)中的校验和初始值设定项字段与页面数据的第200个字节之和,以(页面大小%第200个字节。

要求H35300中使用'%'字符来表示模运算符,就像在C,Java和Javascript这样的编程语言中一样。

同步日志文件

即使使用对日志文件文件句柄xWrite方法(第journalling_a_page节)的调用将数据库页的原始数据写入了日志文件之后,仍不安全地写入数据库文件中的页面。这是因为在系统故障的情况下,写入日志文件的数据仍可能会损坏(请参阅fs_characteristics部分)。在数据库本身内可以更新页面之前,需要执行以下过程:

  1. 调用在日记文件上打开的文件句柄的xSync方法。此操作可确保 将日记文件中的所有日记记录均已写入持久性存储中,并且不会由于后续系统故障而损坏它们。
  2. 日记文件中最近写入的日记标题的日记记录计数字段(请参阅journal_header_format部分 )已更新,以包含自写入标题以来添加到日记文件的日记记录数。
  3. 再次调用xSync方法,以确保对日记记录计数的更新已提交给持久性存储。

如果上面列举的所有三个步骤都成功执行,则可以安全地修改 数据库文件本身中已记录日志的数据库页面的内容。以上三个步骤的组合称为同步日志文件

当需要同步日志文件时,SQLite将调用在日志文件上打开的文件句柄的xSync方法。

当需要同步日记文件时,在按照H35750的要求调用xSync方法之后,SQLite应更新最近写入 日记文件日记头记录计数。必须更新4字节字段,以包含自写入日志头以来已写入 日志文件日志记录数,其格式为4字节big-endian无符号整数。

当需要同步日志文件时,在按照H35760的要求更新日志头记录计数字段之后,SQLite应调用在日志文件上打开的文件句柄的xSync方法。

升级到排他锁

页面高速缓存中修改的页面内容可以写入数据库文件之前,必须在数据库文件上保留排他锁。此锁定的目的是防止在第一个连接写入数据库文件时,另一个连接从数据库文件中读取数据。写入数据库文件的原因是由于正在提交事务,还是要释放页高速缓存中的空间,升级到 独占锁总是在同步日志文件后立即进行 。

当需要作为写事务的一部分升级到排他锁时, 如果尚未通过调用数据库文件上打开的文件句柄的xLock方法持有未锁定的锁,SQLite将首先尝试获取该数据库文件上的挂起锁

当需要作为写事务的一部分升级到排他锁时,在成功获得挂起的锁之后, SQLite应尝试通过调用在数据库文件上打开的文件句柄的xLock方法来获取排他锁

如果无法获得排他锁怎么办?从保留锁升级到挂起的锁的尝试不可能失败。

进行交易

提交写事务是更新数据库文件的最后一步。提交事务是一个七个步骤的过程,概述如下:

  1. 数据库文件头更改计数器字段增加。ff_sqlitert_requirements中描述 的更改计数器cache_validation部分中描述的缓存验证过程 使用

  2. 日志文件的同步。syncing_journal_file部分中介绍了 同步日志文件所需的步骤。

  3. 升级为排它锁对数据库文件,如果一个 排它锁尚未持有。升级到一个 排它锁在部分中描述 upgrading_to_exclusive_lock

  4. 将存储在页面缓存中的所有脏页面的内容复制到数据库文件中。一组脏页以页码顺序写入数据库文件,以提高性能( 有关详细信息,请参见fs_performance节中的假设)。

  5. 同步数据库文件以确保所有更新都安全地存储在永久性介质上。

  6. 日志文件 上打开的文件句柄被关闭,日志文件本身被删除。在这一点上,写入事务 事务已被不可撤销地执行。

  7. 数据库文件已解锁。

进一步解释上面的内容。

以下要求更详细地描述了上面列举的步骤。

当需要提交写事务时,SQLite应当修改第1页以增加存储在数据库文件标头更改计数器 字段中的值。

改变计数是存储在字节4字节大端整场抵消的24数据库文件。到页面1由H35800所需的修饰是使用在部分中描述的方法制备 modifying_appending_truncating。如果尚未将页1记录为当前写事务的一部分,则增加更改计数器可能需要将页1记录为日志。在所有情况下,作为增加更改计数器值的一部分,与页1对应的页缓存条目将变为脏页

当需要提交写事务时,在增加更改计数器字段之后,SQLite应同步日志文件

当需要提交写事务时,按照H35810的要求同步日志文件后,如果 尚未保留数据库文件上的排他锁,则SQLite应尝试 升级到排他锁

当需要提交写事务时,在按照H35810的要求同步日志文件并确保按H35830的要求 对数据库文件持有排他锁之后,SQLite应将 存储在页面缓存中的所有脏页面的内容复制到该数据库文件使用到的xWrite方法调用的数据库连接文件句柄。每次对xWrite的调用都应将单个脏页的内容 (数据的页面大小字节)写入数据库文件。脏页应按从低到高的页码顺序写入。

当需要提交写事务时,在按照H35830的要求将任何脏页的内容复制到数据库文件之后,SQLite应通过调用数据库连接文件句柄的xSync方法来同步数据库文件。

当需要提交写事务时,按照H35840的要求同步数据库文件后,SQLite应关闭对日志文件打开的文件句柄,并通过调用VFS xDelete方法从文件系统中删除 日志文件

当需要提交写事务时,按照H35850的要求删除日记文件后,SQLite应通过调用数据库连接文件句柄的xUnlock方法来放弃对数据库文件持有的所有锁。

提交写事务后是否持有共享锁?

清除脏页

通常,在用户提交活动的写事务之前,实际上不会将任何数据写入数据库文件。例外是,如果单个写入事务包含太多修改以致无法存储在页面缓存中。在这种情况下,在提交事务之前,必须将存储在页面缓存中的某些数据库文件修改应用于数据库文件,以便可以将相关的页面缓存条目从页面缓存中清除到可用内存中。在page_cache_algorithms部分中介绍了何时达到此条件并必须清除脏页的 确切时间

页面高速缓存条目的内容可以写入数据库文件之前,页面高速缓存条目必须满足page_cache_algorithms部分中定义 的可写脏页面的条件。如果page_cache_algorithms部分中的算法选择了脏页进行清除,则需要SQLite同步日志文件。同步日志文件后,与数据库连接关联的所有脏页都立即 被归类为可写脏页

当需要从 页面缓存中清除不可写的脏页面时,SQLite必须在继续执行H35670要求的写操作之前同步日志文件

按照H35640的要求同步日志文件 后,SQLite应 在继续执行H35670要求的写操作之前将新的日志头附加到日志文件中

Writing_journal_header部分中描述了 将新日记帐标题添加到日记文件中。

一旦清除的脏页可写,就可以将其简单地写入数据库文件中。

当需要清除为脏页面页面缓存条目时, SQLite应使用对数据库连接文件句柄的xWrite方法的单个调用将页面数据写入数据库文件。

多文件交易

结单交易

回滚

热门期刊回滚

交易回滚

语句回滚

参考

[1] C API要求文档。
[2] SQL要求文档。
[3] 文件格式要求文档。