首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Temporary Files Used By SQLite

1.介绍

2.九种临时文件

2.1.回滚日志

2.2.预写日志(WAL)文件

2.3.共享内存文件

2.4.主日志文件

2.5.语句日记文件

2.6.TEMP数据库

2.7.视图和子查询的实现

2.8.瞬态指数

2.9.瞬时数据库由VACUUM使用

3. SQLITE_TEMP_STORE编译时间参数和Pragma

4.其他临时文件优化

5.临时文件存储位置

SQLite的一个独特功能是数据库由单个磁盘文件组成。这简化了SQLite的使用,因为移动或备份数据库就像复制单个文件一样简单。它也使得SQLite适合用作应用程序文件格式。但是,当一个完整的数据库保存在单个磁盘文件中时,SQLite在处理数据库的过程中会使用许多临时文件。

本文介绍SQLite创建和使用的各种临时文件。它描述了何时创建文件,何时删除文件,如何使用它们,为什么它们很重要,以及如何避免在创建临时文件昂贵的系统中使用这些文件。

SQLite使用临时文件的方式不被视为SQLite与应用程序之间的合同的一部分。本文档中的信息是SQLite在编写或上次更新本文档时如何运行的正确说明。但是,不能保证未来版本的SQLite将以相同的方式使用临时文件。新版本的临时文件可能会被采用,并且一些当前的临时文件用途可能会在以后的SQLite版本中停用。

SQLite目前使用九种不同类型的临时文件:

  • 回滚日志
  • 硕士期刊
  • 预写日志(WAL)文件
  • 共享内存文件
  • 陈述期刊
  • TEMP数据库
  • 视图和子查询的实现
  • 瞬态指数
  • VACUUM使用的瞬态数据库

关于每个这些临时文件类型的附加信息在续集中。

2.1.回滚日志

回滚日志是用于在SQLite中实现原子提交和回滚功能的临时文件。(有关这种工作方式的详细讨论,请参阅单独的文档,标题为SQLite中的Atomic Commit。)回滚日志始终与数据库文件位于同一目录中,并且与数据库文件具有相同的名称,除了8个字符“ -日志“回滚日志通常在事务首次启动时创建,通常在事务提交或回退时删除回滚日志文件对于实现SQLite的原子提交和回滚功能非常重要没有回滚日志SQLite将无法回滚未完成的事务,并且如果在事务中间发生崩溃或断电,整个数据库可能会在没有回滚日志的情况下损坏。

回滚日志通常分别在事务的开始和结束时创建和销毁。但是这个规则也有例外。

如果事务中间发生崩溃或断电,则回滚日志文件保留在磁盘上。下一次另一个应用程序尝试打开数据库文件时,它会注意到存在已弃用的回滚日志(在这种情况下我们称之为“热日志”),并使用日志中的信息将数据库恢复到其之前的状态未完成交易的开始。这是SQLite如何实现原子提交。

如果应用程序使用杂注将SQLite置于独占锁定模式:

代码语言:javascript
复制
PRAGMA locking_mode=EXCLUSIVE;

SQLite在排他锁定模式会话的第一个事务开始时创建一个新的回滚日志。但在交易结束时,它不会删除回滚日志。回滚日志可能会被截断,或者其标头可能被清零(取决于您使用的SQLite版本),但回滚日志不会被删除。退出独占访问模式之前,回滚日志不会被删除。

journal_mode附注也更改了回滚日记的创建和删除。默认日志模式是DELETE,这是在每个事务结束时删除回滚日志文件的默认行为,如上所述。PERSIST日志模式放弃删除日志文件,而是用零覆盖回退日志头,这样可以防止其他进程回滚日志,从而具有与删除日志文件相同的效果,但无需实际删除文件从磁盘。换句话说,日记模式PERSIST表现出与EXCLUSIVE锁定模式中所看到的相同的行为。OFF日志模式导致SQLite完全忽略回滚日志。换句话说,如果日志模式设置为OFF,则不会写回滚日志。OFF日志模式禁用SQLite的原子提交和回滚功能。设置OFF日志模式时,ROLLBACK命令不可用。如果在使用OFF日志模式的事务中发生崩溃或断电,则无法恢复,数据库文件可能会损坏。MEMORY日志模式会使回滚日志存储在内存中而不是磁盘上。当日志模式为MEMORY时,ROLLBACK命令仍然有效,但由于没有文件存在磁盘上进行恢复,所以在使用MEMORY日志模式的事务中间发生崩溃或断电可能会导致数据库损坏。如果在使用OFF日志模式的事务中发生崩溃或断电,则无法恢复,数据库文件可能会损坏。MEMORY日志模式会使回滚日志存储在内存中而不是磁盘上。当日志模式为MEMORY时,ROLLBACK命令仍然有效,但由于没有文件存在磁盘上进行恢复,所以在使用MEMORY日志模式的事务中间发生崩溃或断电可能会导致数据库损坏。如果在使用OFF日志模式的事务中发生崩溃或断电,则无法恢复,数据库文件可能会损坏。MEMORY日志模式会使回滚日志存储在内存中而不是磁盘上。当日志模式为MEMORY时,ROLLBACK命令仍然有效,但由于没有文件存在磁盘上进行恢复,所以在使用MEMORY日志模式的事务中间发生崩溃或断电可能会导致数据库损坏。

2.2.预写日志(WAL)文件

当SQLite在WAL模式下运行时,使用预写日志或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()调用中标识的数据库)一个随机后缀。主日志文件包含在事务处理期间更改的所有附加辅助数据库的名称。当主日志文件被删除时,多数据库事务提交。有关更多详细信息,请参阅标题为“Atomic Commit In SQLite”的文档。

如果没有主日志,多数据库事务上的事务提交对每个数据库都是原子的,但在所有数据库中不会是原子的。换句话说,如果提交在中途因崩溃或断电而中断,则对其中一个数据库的更改可能会完成,而对另一个数据库的更改可能会回滚。主日志会导致所有数据库中的所有更改都回滚或一起提交。

主日志文件仅针对涉及多个数据库文件的COMMIT操作创建,其中至少有两个数据库满足以下所有要求:

  • 数据库由事务修改
  • PRAGMA同步设置不关闭
  • PRAGMA journal_mode不是OFF,MEMORY或WAL

这意味着,当数据库文件关闭同步或使用OFF,MEMORY或WAL日志模式时,SQLite事务在断电时不会在多个数据库文件间原子化。对于同步OFF和对于journal_mode OFF和MEMORY,如果事务提交被断电中断,数据库通常会损坏。对于WAL模式,单个数据库文件在断电时自动更新,但在多文件事务中,某些文件可能会回滚,而其他文件在恢复供电后会前滚。

2.5.语句日记文件

语句日志文件用于在较大的事务中回滚单个语句的部分结果。例如,假设UPDATE语句将尝试修改数据库中的100行。但是,在修改前50行后,UPDATE会触发一个约束违规行为,该行为会阻止整个语句。语句日志用于撤消前50行更改,以便数据库恢复到语句开始时的状态。

语句日志仅针对UPDATE或INSERT语句创建,该语句可能会更改数据库的多行,并且可能触发触发器中的约束或RAISE异常,因此需要撤消部分结果。如果UPDATE或INSERT未包含在BEGIN ... COMMIT内,并且在同一数据库连接上没有其他活动语句,则不会创建语句日志,因为可以使用普通回滚日志。如果使用替代冲突解决算法,语句日志也会被忽略。例如:

代码语言:javascript
复制
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”语句出现时立即创建。这个单独的临时数据库文件也有一个关联的回滚日志。当使用sqlite3_close()关闭数据库连接时,用于存储TEMP表的临时数据库文件将自动删除。

TEMP数据库文件与使用ATTACH语句添加的辅助数据库文件非常相似,但具有一些特殊属性。当数据库连接关闭时,TEMP数据库始终会自动删除。TEMP数据库始终使用同步= OFF和journal_mode = PERSIST PRAGMA设置。而且,TEMP数据库不能与DETACH一起使用,也不能使用另一个进程附加TEMP数据库。

只有当应用程序使用“CREATE TEMP TABLE”语句时,才会创建与TEMP数据库及其回滚日志关联的临时文件。

2.7.视图和子查询的实现

包含子查询的查询必须有时分别评估子查询并将结果存储在临时表中,然后使用临时表的内容来评估外部查询。我们称之为“实现”子查询。SQLite中的查询优化器试图避免实现,但有时不容易避免。通过实现创建的临时表各自存储在它们自己的单独临时文件中,该文件在查询结束时自动删除。当然,这些临时表的大小取决于子查询实现中的数据量。

IN操作符右侧的子查询必须经常实现。例如:

代码语言:javascript
复制
SELECT * FROM ex1 WHERE ex1.a IN (SELECT b FROM ex2);

在上面的查询中,计算子查询“SELECT b FROM ex2”,并将其结果存储在临时表(实际上是一个临时索引)中,该临时表允许使用简单的二分查找来确定值ex2.b是否存在。一旦构建了该表,就会运行外部查询,并对每个预期结果行进行检查,以查看临时表中是否包含ex1.a。仅当检查为真时才输出该行。

为避免创建临时表,可能会将查询重写为:

代码语言:javascript
复制
SELECT * FROM ex1 WHERE EXISTS(SELECT 1 FROM ex2 WHERE ex2.b=ex1.a);

最新版本的SQLite(版本3.5.4 2007-12-14)及更高版本)将在列ex2.b上存在索引时自动执行此重写。

如果IN运算符的右侧可以是以下值的列表值:

代码语言:javascript
复制
SELECT * FROM ex1 WHERE a IN (1,2,3);

IN右侧的列表值被视为必须实现的子查询。换句话说,前面的陈述就像是:

代码语言:javascript
复制
SELECT * FROM ex1 WHERE a IN (SELECT 1 UNION ALL
                              SELECT 2 UNION ALL
                              SELECT 3);

当右侧是值列表时,临时索引始终用于保存IN运算符右侧的值。

子查询在出现在SELECT语句的FROM子句中时可能还需要实现。例如:

代码语言:javascript
复制
SELECT * FROM ex1 JOIN (SELECT b FROM ex2) AS t ON t.b=ex1.a;

根据查询,SQLite可能需要将“(SELECT b FROM ex2)”子查询实现为临时表,然后执行ex1和临时表之间的连接。查询优化器试图通过“扁平化”查询来避免这种情况。在前面的例子中,查询可以变平,SQLite会自动将查询转换为

代码语言:javascript
复制
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关键字
  • 复合SELECT语句由UNION,EXCEPT或INTERSECT连接

每个瞬态索引都存储在它自己的临时文件中。临时索引的临时文件在使用该临时文件的语句末尾自动删除。

SQLite努力使用预先存在的索引来实现ORDER BY子句。如果适当的索引已经存在,SQLite将遍历索引而不是基础表来提取请求的信息,从而使行按照所需的顺序出来。但是,如果SQLite找不到合适的索引,它将评估查询并将每行存储在临时索引中,该临时索引的数据是行数据,其关键字是ORDER BY术语。在对查询进行求值后,SQLite返回并从头到尾遍历瞬态索引,以便按照所需顺序输出行。

SQLite通过按照GROUP BY条款建议的顺序对输出行进行排序来实现GROUP BY。每个输出行都与之前进行比较,看它是否启动一个新的“组”。GROUP BY条款的排序与ORDER BY条款的排序完全相同。如果可能的话,使用先前存在的索引,但是如果没有合适的索引可用,则创建瞬态索引。

聚合查询中的DISTINCT关键字通过在临时文件中创建临时索引并将每个结果行存储在该索引中实现。当计算新的结果行时,会检查它们是否已经存在于临时索引中,并且如果它们确实放弃了新的结果行。

用于复合查询的UNION运算符通过在临时文件中创建临时索引并将左侧和右侧子查询的结果存储在临时索引中来实现,从而丢弃重复项。在对两个子查询进行评估之后,瞬态索引从开始到结束进行,以生成最终输出。

用于复合查询的EXCEPT运算符通过在临时文件中创建临时索引来实现,将左侧子查询的结果存储在此临时索引中,然后从临时子索引中从临时索引中除去结果,最后将索引从开始结束以获得最终输出。

用于复合查询的INTERSECT运算符是通过创建两个单独的临时索引来实现的,每个索引都在单独的临时文件中。左侧和右侧子查询分别被评估为单独的瞬态索引。然后这两个指数一起行走,输出出现在两个指数中的条目。

请注意,用于复合查询的UNION ALL操作符本身不使用临时索引(尽管UNION ALL的右侧和左侧子查询当然可以使用临时索引,这取决于它们是如何组成的。)

2.9.瞬时数据库由VACUUM使用

VACUUM命令通过创建临时文件,然后将整个数据库重建到该临时文件中工作。然后将临时文件的内容复制回原始数据库文件,并删除临时文件。

VACUUM命令创建的临时文件仅存在于命令本身的持续时间内。临时文件的大小不会超过原始数据库的大小。

与事务控制相关的临时文件(即回滚日志,主日志,预写日志(WAL)文件和共享内存文件)始终写入磁盘。但其他类型的临时文件可能仅存储在内存中,并且从不写入磁盘。除了回滚,主语句和语句日志以外的临时文件是写入磁盘还是仅存储在内存中,取决于SQLITE_TEMP_STORE编译时参数,temp_store编译指示以及临时文件的大小。

SQLITE_TEMP_STORE编译时参数是一个#define,其值是一个介于0和3之间(包括0和3)的整数。SQLITE_TEMP_STORE编译时参数的含义如下所示:

  • 临时文件总是存储在磁盘上,而不管temp_store编译指示的设置如何。
  • 临时文件默认存储在磁盘上,但可以通过temp_store编译指示覆盖。
  • 临时文件默认存储在内存中,但可以由temp_store杂注覆盖。
  • 无论temp_store编译指示的设置如何,临时文件总是存储在内存中。

SQLITE_TEMP_STORE编译时参数的默认值为1,表示将临时文件存储在磁盘上,但提供使用temp_store附注重写行为的选项。

temp_store编译指示具有一个整数值,该值也影响存储临时文件的位置的决定。temp_store pragma的值具有以下含义:

  • 根据SQLITE_TEMP_STORE编译时参数确定的临时文件使用磁盘或内存存储。
  • 如果SQLITE_TEMP_STORE编译时参数为临时文件指定了内存存储,则覆盖该决定并改为使用磁盘存储。否则,请遵循SQLITE_TEMP_STORE编译时参数的建议。
  • 如果SQLITE_TEMP_STORE编译时参数为临时文件指定磁盘存储,则覆盖该决定并改为使用内存存储。否则,请遵循SQLITE_TEMP_STORE编译时参数的建议。

temp_store编译指示的默认设置为0,这意味着遵循SQLITE_TEMP_STORE编译时参数的建议。

重申一下,SQLITE_TEMP_STORE编译时参数和temp_store编译指示只影响回滚日志和主日志以外的临时文件。无论SQLITE_TEMP_STORE编译时参数和temp_store编译指示的设置如何,回滚日志和主日志始终写入磁盘。

SQLite使用最近读取和写入数据库页面的页面缓存。此页面缓存不仅用于主数据库文件,而且还用于存储在临时文件中的临时索引和表格。如果SQLite需要使用临时索引或表,并且SQLITE_TEMP_STORE编译时参数和temp_store编译指示被设置为在磁盘上存储临时表和索引,则信息最初仍然存储在页面缓存中的内存中。临时文件未打开,并且信息在页面缓存满后才真正写入磁盘。

这意味着对于临时表和索引很小(足够小以适应页面缓存)的很多常见情况,不会创建临时文件,也不会发生磁盘I / O。只有当临时数据变得太大而不能放入RAM时,信息才会泄漏到磁盘。

每个临时表和索引都有自己的页面缓存,可以存储由SQLITE_DEFAULT_TEMP_CACHE_SIZE编译时间参数确定的最大数量的数据库页面。(默认值为500页。)每个临时表和索引页面缓存中的最大数据库页数是相同的。该值不能在运行时或以每个表或每个索引为基础进行更改。每个临时文件都有自己的专用页面缓存,并拥有自己的SQLITE_DEFAULT_TEMP_CACHE_SIZE页面限制。

在其中创建临时文件的目录或文件夹由特定于操作系统的VFS确定。

在类Unix系统上,按以下顺序搜索目录:

  • 由PRAGMA temp_store_directory或sqlite3_temp_directory全局变量设置的目录
  • SQLITE_TMPDIR环境变量
  • TMPDIR环境变量
  • / var / tmp中
  • 在/ usr / tmp目录
  • / tmp目录
  • 当前工作目录(“。”)
  • 使用上面发现的第一个存在并且设置了写入和执行位。最后的“。” 回退对于某些在chroot jail中使用SQLite的应用程序非常重要,因为这些应用程序没有可用的标准临时文件位置。

在Windows系统上,按以下顺序搜索文件夹:

  • 由PRAGMA temp_store_directory设置的文件夹或由sqlite3_temp_directory全局变量设置的文件夹
  • 由GetTempPath()系统接口返回的文件夹。

在这种情况下,SQLite本身并没有注意环境变量,尽管大概是GetTempPath()系统调用。CYGWIN构建的搜索算法不同。查看源代码以获取详细信息。

代码语言:javascript
复制
 SQLite在公共领域。

扫码关注腾讯云开发者

领取腾讯云代金券