返回:SQLite—系列文章目录
上一篇:SQLite数据库文件损坏的可能几种情况
下一篇:未发表
1. 引言
SQLite的显着特点之一它是一个数据库由一个磁盘文件组成。 这简化了 SQLite 的使用,因为移动或备份 数据库就像复制单个文件一样简单。它还使 SQLite适合用作应用程序文件格式。 但是,当一个完整的数据库保存在单个磁盘文件中时, SQLite确实在处理数据库的过程。
本文介绍SQLite的各种临时文件创建和使用。它描述了何时创建文件,何时它们被删除,它们的用途,为什么它们很重要, 以及如何在创建临时文件的系统上避免它们陷阱。
不考虑SQLite使用临时文件的方式 SQLite与应用程序签订的合约的一部分。这 本文档中的信息是对如何操作的正确描述 SQLite在编写本文档时或最后编写时运行更新。但是不能保证SQLite的未来版本 将以相同的方式使用临时文件。新型临时可能会使用文件,并且一些当前的临时文件使用可能会停止在SQLite 的未来版本中。
2. 九种临时文件
SQLite目前使用九种不同类型的临时文件:
- 回滚日志
- 超级期刊
- 预写日志 (WAL) 文件
- 共享内存文件
- 报表日记帐
- TEMP 数据库
- 视图和子查询的具体化
- 瞬态指数
- VACUUM 使用的瞬态数据库
有关每个这些临时文件类型的附加信息 在续集中。
2.1. 回滚日志
回滚日志是用于实现的临时文件 SQLite中的原子提交和回滚功能。(有关其工作原理的详细讨论,请参阅 标题为 Atomic Commit In SQLite 的单独文档。 回滚日志始终位于同一目录中作为数据库文件,并且与数据库同名文件,但附加了 8 个字符的“-journal”除外。 回滚日志通常在事务发生时创建首先启动,通常在事务时被删除 提交或回滚。 回滚日志文件对于实现 SQLite的原子提交和回滚功能。没有 回滚日志,SQLite 将无法回滚 事务不完整,以及是否发生崩溃或断电在事务过程中,整个数据库可能会在没有回滚日志的情况下损坏。
回滚日志通常在事务的开始和结束。但也有例外到这个规则。
如果在交易过程中发生崩溃或断电, 然后,回滚日志文件将保留在磁盘上。下次 另一个应用程序尝试打开数据库文件,它注意到 被遗弃的回滚日志的存在(我们称之为“热 journal“,并使用 日志将数据库还原到启动前的状态 未完成的交易。这就是SQLite的实现方式 原子提交。
如果应用程序使用 选项:
PRAGMA locking_mode=EXCLUSIVE;
SQLite 在第一个 独占锁定模式会话中的事务。但是在 事务结束时,它不会删除回滚 杂志。回滚日志可能被截断,或者其标头可能被截断 可能归零(取决于您使用的 SQLite 版本) 但不会删除回滚日志。回滚日志是 在退出独占访问模式之前不会删除。
回滚日志的创建和删除也会被journal_mode编译指示更改。 默认日记模式为 DELETE,这是默认行为 在每个事务结束时删除回滚日志文件, 如上所述。PERSIST 日志模式放弃删除 日志文件,而是覆盖回滚日志头 使用零,以防止其他进程回滚 日志,因此具有与删除日志文件相同的效果 无需实际从磁盘中删除文件的费用。在其他 words,日志模式 PERSIST 表现出与所见相同的行为 在 EXCLUSIVE 锁定模式下。这 OFF 日志模式会导致 SQLite 完全省略回滚日志。 换言之,如果日志模式为 设置为 OFF。 OFF 日志模式禁用原子 SQLite的提交和回滚功能。ROLLBACK 命令 设置了 OFF 日志模式时不可用。如果发生碰撞或电源 损失发生在使用 OFF 日志的事务中间 模式下,无法恢复,数据库文件可能会 去腐败。 MEMORY 日志模式导致回滚日志存储在内存而不是磁盘。ROLLBACK 命令在以下情况下仍然有效日志模式是 MEMORY,但因为磁盘上不存在 恢复、崩溃或断电 在使用 MEMORY 日志模式可能会导致数据库损坏。
2.2. 预写日志(WAL)文件
使用预写日志或 WAL 文件代替回滚日志 当 SQLite 在 WAL 模式下运行时。与回滚日志一样, WAL 文件的目的是实现原子提交和回滚。 WAL 文件始终位于同一目录中 作为数据库文件,并且与数据库同名 文件,但附加了 4 个字符“-wal”的文件除外。 WAL 文件是在首次连接到 数据库被打开,通常在最后 与数据库的连接将关闭。但是,如果最后一个连接 没有干净地关机,WAL文件将保留在文件系统中 并将在下次数据库时自动清理 打开。
2.3. 共享内存文件
在 WAL 模式下运行时,所有 SQLite 数据库连接都关联 使用相同的数据库文件需要共享一些用作 WAL 文件的索引。在大多数实现中,此共享内存是 通过在为此目的创建的文件上调用 mmap() 来实现: 共享内存文件。共享内存文件(如果存在)位于 与数据库文件位于同一目录中,并且与 数据库文件,但附加了 4 个字符“-shm”的除外。 共享内存文件仅在 WAL 模式下运行时存在。
共享内存文件不包含持久性内容。唯一的目的 的共享内存文件是提供共享内存块以供使用 通过多个进程在 WAL 模式下访问同一数据库。 如果 VFS 能够提供访问共享的替代方法 内存,则可以使用替代方法而不是 共享内存文件。例如,如果 PRAGMA locking_mode设置为 EXCLUSIVE(表示只有一个进程能够访问数据库 文件),则共享内存将从堆而不是从堆中分配 共享内存文件,而共享内存文件永远不会 创建。
共享内存文件与其关联的 WAL 文件具有相同的生存期。 共享内存文件是在创建 WAL 文件时创建的,并且是 删除 WAL 文件时删除。在 WAL 文件恢复期间, 共享内存文件是根据 正在恢复的 WAL 文件。
2.4. 超级日志文件
超级日志文件用作原子提交的一部分 当单个事务对多个事务进行更改时的处理 已使用 ATTACH 语句添加到单个数据库连接的数据库。超级日志文件始终是 与主数据库文件位于同一目录中 (主数据库文件是标识的数据库 在创建数据库连接的原始 sqlite3_open()、sqlite3_open16() 或 sqlite3_open_v2() 调用中) 带有随机后缀。超级日志文件包含 所有附加的辅助数据库的名称 在交易过程中已更改。多数据库 删除超级日志文件时提交事务。 请参阅标题为“在 SQLite 中进行原子提交”的文档。 其他细节。
如果没有超级日志,事务将提交到多数据库上 事务对于每个数据库都是原子的,但它 不会在所有数据库中都是原子的。换言之,如果 提交在中间因崩溃或断电而中断,然后 对其中一个数据库的更改可能会完成,而更改 可能会回滚到另一个数据库。超级期刊导致所有 将所有数据库中的更改进行回滚或一起提交。
超级日志文件仅用于以下 COMMIT 操作 涉及多个数据库文件,其中至少两个数据库 满足以下所有要求:
- 数据库由事务修改
- PRAGMA 同步设置未关闭
- PRAGMA journal_mode不是 OFF、MEMORY 或 WAL
这意味着 SQLite 事务不是原子的 跨多个数据库文件断电时,数据库 文件已关闭同步或使用日记时 OFF、MEMORY 或 WAL 模式。用于同步 OFF 和 journal_modes OFF 和 MEMORY,如果出现以下情况,数据库通常会损坏 事务提交因断电而中断。对于 WAL 模式,单个数据库文件以原子方式更新 在断电的情况下,但在多文件事务的情况下, 某些文件可能会回滚,而其他文件可能会在之后向前滚动 电源已恢复。
2.5. 报表日志文件
语句日志文件用于回滚 大型事务中的单个语句。例如,假设 UPDATE 语句将尝试修改数据库中的 100 行。 但是在修改前 50 行后,UPDATE 命中 约束冲突,应阻止整个语句。 语句日志用于撤消前 50 行更改 以便将数据库还原到开始时的状态 的声明。
仅为 UPDATE 或 INSERT 语句创建语句日记帐 这可能会更改数据库的多行,并且可能会命中 约束或触发器中的 RAISE 异常,因此需要 撤消部分结果。 如果 UPDATE 或 INSERT 不包含在 BEGIN 中...COMMIT 和 if 那么,同一数据库连接上没有其他活动语句 自 Ordinary 以来,没有创建任何报表日记 可以改用回滚日志。 如果替代冲突解决算法是 使用。例如:
UPDATE OR FAIL ... UPDATE OR IGNORE ... UPDATE OR REPLACE ... UPDATE OR ROLLBACK ... INSERT OR FAIL ... INSERT OR IGNORE ... INSERT OR REPLACE ... INSERT OR ROLLBACK ... REPLACE INTO ....
语句日记被赋予一个随机名称,不一定 在与主数据库相同的目录中,并且是自动的 在交易结束时删除。的大小 报表日记帐与实施的更改大小成正比 通过导致语句日志的 UPDATE 或 INSERT 语句 要创建。
2.6. TEMP数据库
使用“CREATE TEMP TABLE”语法创建的表仅 对数据库连接可见,其中“CREATE TEMP TABLE” 语句最初是计算的。这些 TEMP 表一起 与任何关联的索引、触发器和视图一起 存储在单独的临时数据库文件中,该文件创建为 一旦看到第一个“CREATE TEMP TABLE”语句。 这个单独的临时数据库文件还具有关联的 回滚日志。 删除用于存储 TEMP 表的临时数据库文件 当数据库连接关闭时自动 使用 sqlite3_close()。
TEMP 数据库文件与辅助数据库非常相似 使用 ATTACH 语句添加的文件,尽管有一些 特殊属性。 当数据库连接关闭时,始终会自动删除 TEMP 数据库。 TEMP 数据库始终使用 synchronous=OFF 和 journal_mode=PERSIST PRAGMA 设置。 而且,TEMP 数据库不能与 DETACH 一起使用,也不能 另一个进程 ATTACH TEMP 数据库。
与 TEMP 数据库关联的临时文件及其 仅当应用程序使用 “CREATE TEMP TABLE”语句。
2.7. 视图和子查询的具体化
包含子查询的查询有时必须计算 子查询分开,并将结果存储在临时 表,然后使用临时表的内容进行评估 外部查询。 我们称之为“物化”子查询。 SQLite 中的查询优化器试图避免实现, 但有时它并不容易避免。 通过具体化创建的临时表将分别存储 在他们自己单独的临时文件中,这是自动的 在查询结束时删除。 这些临时表的大小取决于 当然,数据在子查询的具体化中。
IN 运算符右侧的子查询必须经常 被物化。例如:
SELECT * FROM ex1 WHERE ex1.a IN (SELECT b FROM ex2);
在上面的查询中,将评估子查询“SELECT b FROM ex2” 其结果存储在一个临时表(实际上是一个临时表)中 index),允许确定值 ex2.b 使用简单的二进制搜索存在。构建此表后, 运行外部查询,并针对每个预期结果行进行检查 用于查看 ex1.a 是否包含在临时表中。 仅当 check 为 true 时,才会输出该行。
为了避免创建临时表,可能会重写查询 如下:
SELECT * FROM ex1 WHERE EXISTS(SELECT 1 FROM ex2 WHERE ex2.b=ex1.a);
最新版本的 SQLite(版本 3.5.4,2007-12-14)及更高版本) 将自动执行此重写 如果列 ex2.b 上存在索引。
如果 IN 运算符的右侧可以是值列表 如下所示:
SELECT * FROM ex1 WHERE a IN (1,2,3);
IN 右侧的列表值被视为 必须具体化的子查询。换言之, 上一个语句的行为好像是:
SELECT * FROM ex1 WHERE a IN (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3);
临时索引始终用于保存 IN 运算符的右侧,当该右侧 是值的列表。
子查询在出现时可能还需要具体化 在 SELECT 语句的 FROM 子句中。例如:
SELECT * FROM ex1 JOIN (SELECT b FROM ex2) AS t ON t.b=ex1.a;
根据查询,SQLite 可能需要实现 “(SELECT b FROM ex2)” 子查询到临时表中,然后 执行 ex1 和临时表之间的联接。这 查询优化器试图通过“扁平化”来避免这种情况 查询。在前面的示例中,查询可以扁平化, SQLite 会自动将查询转换为
SELECT ex1.*, ex2.b FROM ex1 JOIN ex2 ON ex2.b=ex1.a;
更复杂的查询可能会也可能不会使用查询 展平以避免临时表。无论是否 查询是否可以扁平化取决于以下因素:是否 或者子查询或外部查询是否包含聚合函数, ORDER BY 或 GROUP BY 子句、LIMIT 子句等。 查询何时可以和不能扁平化的规则是 非常复杂,超出了本文档的范围。
2.8. 瞬态指数
SQLite可以利用瞬态索引来 实现 SQL 语言功能,例如:
- ORDER BY 或 GROUP BY 子句
- 聚合查询中的 DISTINCT 关键字
- 由 UNION、EXCEPT 或 INTERSECT 联接的复合 SELECT 语句
每个瞬态索引都存储在其自己的临时文件中。 暂时性索引的临时文件将自动删除 在使用它的语句的末尾。
SQLite努力使用预先存在的ORDER BY子句来实现 指数。如果已经存在适当的索引,SQLite 将行走 索引,而不是基础表,以提取 请求的信息,从而导致行出现在 所需的顺序。但是如果SQLite找不到合适的索引 它将评估查询并将每一行存储在瞬态索引中 其数据是行数据,其键是 ORDER BY 项。 评估查询后,SQLite 返回并遍历 从头到尾的瞬态索引,以便输出 按所需顺序排列的行。
SQLite 通过对 GROUP BY条款建议的顺序。每个输出行是 与前一个相比,看看它是否启动了一个新的“组”。 按 GROUP BY 术语排序的方式完全相同 作为按 ORDER BY 条款排序。使用预先存在的索引 如果可能,但如果没有合适的索引可用,则暂时性 索引已创建。
聚合查询上的 DISTINCT 关键字由 在临时文件中创建暂时性索引并存储 该索引中的每个结果行。计算新结果行时 进行检查以查看它们是否已存在于瞬态中 索引,如果这样做,则丢弃新的结果行。
复合查询的 UNION 运算符是通过创建 临时文件中的暂时性索引并存储结果 暂时索引中的左子查询和右子查询,丢弃 重复。在评估了两个子查询后, 瞬态索引从头到尾遍历以生成最终输出。
复合查询的 EXCEPT 运算符是通过创建 临时文件中的暂时性索引,用于存储 在此瞬态索引中左键查询,然后删除结果 从瞬态索引的右子查询,最后步行 索引从头到尾得到最终输出。
复合查询的 INTERSECT 运算符由 创建两个单独的瞬态索引,每个索引都位于单独的 临时文件。计算左侧和右侧子查询 每个都变成一个单独的瞬态索引。然后是两个指数 一起行走,并出现在两个索引中的条目 是输出。
请注意,复合查询的 UNION ALL 运算符不 单独使用瞬态索引(当然是正确的 UNION ALL 的左子查询可能使用瞬态索引 取决于它们的组成方式。
2.9. VACUUM使用的瞬态数据库
VACUUM 命令的工作原理是创建一个临时文件 然后将整个数据库重建为该临时数据库 文件。然后,将临时文件的内容复制回去 放入原始数据库文件中,临时文件是 删除。
由 VACUUM 命令创建的临时文件仅存在 在命令本身的持续时间内。临时的大小 文件不会比原始数据库大。
3. 编译时参数和编译SQLITE_TEMP_STORE
与事务控制关联的临时文件,即 回滚日志、超级日志、预写日志 (WAL) 文件、 和共享内存文件,始终写入磁盘。 但其他类型的临时文件可能存储在内存中 只有并且从未写入磁盘。 无论是否是回滚以外的临时文件, super 和语句日志写入磁盘或仅存储在内存中 取决于 SQLITE_TEMP_STORE编译时参数,temp_store Pragma, 以及临时文件的大小。
SQLITE_TEMP_STORE 编译时参数 #define 是一个值为 介于 0 和 3 之间的整数(含 0 和 3)。SQLITE_TEMP_STORE编译时参数的含义如下:
- 无论设置如何,临时文件始终存储在磁盘上 temp_store pragma。
- 默认情况下,临时文件存储在磁盘上,但这可以是 被 temp_store 编译指示覆盖。
- 默认情况下,临时文件存储在内存中,但这可以是 被 temp_store 编译指示覆盖。
- 无论设置如何,临时文件始终存储在内存中 temp_store pragma。
SQLITE_TEMP_STORE compile-time 参数的默认值为 1, 这意味着将临时文件存储在磁盘上,但提供选项 使用temp_store编译指示覆盖行为。
编译指示temp_store 一个整数值,该值也会影响存储位置的决策 临时文件。temp_store pragma 的值具有 含义如下:
- 根据确定,对临时文件使用磁盘或内存存储 通过 SQLITE_TEMP_STORE 编译时参数。
- 如果 SQLITE_TEMP_STORE compile-time 参数指定 临时文件,然后覆盖该决定并改用磁盘存储。 否则,请遵循 SQLITE_TEMP_STORE 编译时的建议 参数。
- 如果 SQLITE_TEMP_STORE compile-time 参数指定了 临时文件,然后覆盖该决定并改用内存存储。 否则,请遵循 SQLITE_TEMP_STORE 编译时的建议 参数。
temp_store 编译指示的默认设置为 0, 这意味着遵循SQLITE_TEMP_STORE编译时的建议 参数。
重申一下,SQLITE_TEMP_STORE compile-time 参数和仅temp_store编译指示 影响回滚日志以外的临时文件 和超级期刊。回滚日志和 超级日志始终写入磁盘,无论 SQLITE_TEMP_STORE 编译时参数和temp_store编译指示。
4. 其他临时文件优化
SQLite使用最近读取和写入的数据库的页面缓存 页面。此页面缓存不仅用于主数据库 文件,但也适用于暂时性索引和存储在临时 文件。如果 SQLite 需要使用临时索引或表,并且 SQLITE_TEMP_STORE 编译时参数和temp_store编译指示是 设置为在磁盘上存储临时表和索引,信息 最初仍存储在页面缓存的内存中。这 临时文件未打开,信息不真实 写入磁盘,直到页面缓存已满。
这意味着对于许多常见情况,临时表 索引很小(小到可以放入页面缓存) 不会创建临时文件,也不会发生磁盘 I/O。只 当临时数据变得太大而无法放入 RAM 时 信息溢出到磁盘。
每个临时表和索引都有其自己的页面缓存 可以存储确定的最大数据库页数 通过 SQLITE_DEFAULT_TEMP_CACHE_SIZE 编译时参数。 (默认值为 500 页。 页面缓存中的最大数据库页数为 每个临时表和索引都相同。该值不能 在运行时或按表或按索引进行更改。 每个临时文件都有自己的私有页面缓存,其 拥有SQLITE_DEFAULT_TEMP_CACHE_SIZE页数限制。
5. 临时文件存储位置
在其中创建临时文件的目录或文件夹是 由特定于操作系统的 VFS 确定。
在类 unix 系统上,按以下顺序搜索目录:
- PRAGMA temp_store_directory 或 sqlite3_temp_directory 全局变量设置的目录
- SQLITE_TMPDIR环境变量
- TMPDIR 环境变量
- /var/tmp
- /usr/tmp
- /tmp
- 当前工作目录 (“.”)
上述第一个被发现存在并具有写入和 使用 execute bits set。最后的“.”回退对某些人来说很重要 在 chroot 监狱中使用 SQLite 的应用程序没有 可用的标准临时文件位置。
在 Windows 系统上,按以下顺序搜索文件夹:
- PRAGMA temp_store_directory 或 sqlite3_temp_directory 全局变量设置的文件夹
- GetTempPath() 系统接口返回的文件夹。
SQLite本身并不关注环境变量 在这种情况下,尽管 GetTempPath() 系统调用可能是这样做的。 CYGWIN构建的搜索算法是不同的。选中 源代码了解详情。