Small. Fast. Reliable.
Choose any three.
SQLite使用的临时文件

1.简介

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

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

SQLite使用临时文件的方式不视为SQLite与应用程序签订的合同的一部分。本文档中的信息正确描述了在撰写或最后更新本文档时SQLite的操作方式。但是不能保证将来的SQLite版本将以相同的方式使用临时文件。可能会使用新型的临时文件,并且在将来的SQLite版本中可能会停止使用某些当前的临时文件。

2.九种临时文件

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

  1. 回滚日记
  2. 超级新闻
  3. 预写日志(WAL)文件
  4. 共享内存文件
  5. 声明日记
  6. TEMP数据库
  7. 视图和子查询的实现
  8. 瞬态指数
  9. VACUUM使用的瞬态数据库

有关每种临时文件类型的更多信息,请参见续集。

2.1。回滚日记帐

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

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

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

如果应用程序使用编译指示将SQLite置于 独占锁定模式

PRAGMAlocking_mode =独家的;

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

回退日志的创建和删除也可以通过journal_mode编译指示进行更改 。默认的日志记录模式是DELETE,这是在每个事务结束时删除回滚日志文件的默认行为,如上所述。PERSIST日志模式放弃删除日志文件,而是用零覆盖回滚日志头,这样可以防止其他进程回滚日志,因此与删除日志文件具有相同的效果,尽管实际上并没有删除该日志文件的开销。磁盘中的文件。换句话说,日志模式PERSIST表现出与EXCLUSIVE锁定模式相同的行为。OFF日志模式使SQLite完全省略回滚日志。换句话说,如果日志模式设置为OFF,则不会写入任何回滚日志。OFF日志模式禁用SQLite的原子提交和回滚功能。设置为OFF日志模式时,ROLLBACK命令不可用。而且,如果在使用OFF日志模式的事务中间发生崩溃或断电,则无法进行恢复,并且数据库文件可能会损坏。MEMORY日志模式使回滚日志存储在内存中,而不是磁盘上。当日志模式为MEMORY时,ROLLBACK命令仍然有效,但是由于磁盘上不存在用于恢复的文件,因此在使用MEMORY日志模式的事务中,崩溃或断电很可能导致数据库损坏。MEMORY日志模式使回滚日志存储在内存中,而不是磁盘上。当日志模式为MEMORY时,ROLLBACK命令仍然有效,但是由于磁盘上不存在用于恢复的文件,因此在使用MEMORY日志模式的事务中,崩溃或断电很可能导致数据库损坏。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能够提供一种访问共享内存的替代方法,则可以使用该替代方法而不是共享内存文件。例如,如果PRAGMAlocking_mode设置为EXCLUSIVE(意味着只有一个进程能够访问数据库文件),则共享内存将从堆中分配,而不是从共享内存文件中分配,并且共享内存文件将永远不会被创造。

共享内存文件具有与其关联的WAL文件相同的生存期。共享内存文件在创建WAL文件时创建,并在删除WAL文件时被删除。在WAL文件恢复期间,将基于要恢复的WAL文件的内容从头开始重新创建共享内存文件。

2.4。超级期刊文件

当单个事务对 使用ATTACH语句添加到单个数据库连接中的多个数据库进行更改时,超级日志文件将用作原子提交过程的一部分。超级新闻文件始终与主数据库文件位于同一目录中(主数据库文件是在创建数据库连接的原始sqlite3_open()sqlite3_open16()sqlite3_open_v2()调用中标识的数据库)),并带有随机后缀。超级新闻文件包含在事务期间更改的所有各种附加辅助数据库的名称。删除超级日志文件时,将提交多数据库事务。有关更多详细信息,请参见标题为“ SQLite中的原子提交”的文档 。

如果没有超级日志,则对多数据库事务的事务提交对于每个数据库而言都是原子的,但是对于所有数据库而言,事务都不是原子的。换句话说,如果提交在中间因崩溃或断电而中断,则对一个数据库的更改可能会完成,而对另一个数据库的更改可能会回滚。超级期刊使所有数据库中的所有更改回滚或一起提交。

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

  1. 数据库被事务修改
  2. PRAGMA同步设置不是OFF
  3. PRAGMA journal_mode未关闭,MEMORY,或WAL

这意味着当数据库文件已同步关闭或使用OFF,MEMORY或WAL日志模式时,断电时多个数据库文件之间的SQLite事务不是原子的。对于同步OFF以及对于journal_modes OFF和MEMORY,如果事务提交由于断电而中断,则数据库通常会损坏。对于 WAL模式,单个数据库文件会在断电时自动进行更新,但是在多文件事务的情况下,恢复电源后,某些文件可能会回滚,而另一些文件会回滚。

2.5。报表日记文件

语句日志文件用于回滚较大事务中单个语句的部分结果。例如,假设UPDATE语句将尝试修改数据库中的100行。但是在修改了前50行之后,UPDATE遇到了违反约束的情况,该约束会阻塞整个语句。语句日志用于撤消前50行的更改,以便将数据库还原到语句开始时的状态。

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

更新或失败...
更新或忽略...
更新或替换...
更新或回滚...
插入或失败...
插入或忽略...
插入或更换...
插入或回滚...
更换成....

语句日志被赋予一个随机名称,不必与主数据库位于同一目录中,并且在交易结束时会自动删除。语句日志的大小与导致创建语句日志的UPDATE或INSERT语句实现的更改的大小成比例。

2.6。TEMP数据库

使用“ CREATE TEMP TABLE”语法创建的表仅对最初评估“ CREATE TEMP TABLE”语句的数据库连接可见。这些TEMP表以及所有关联的索引,触发器和视图一起存储在单独的临时数据库文件中,该文件在看到第一个“ CREATE TEMP TABLE”语句后即会创建。此单独的临时数据库文件还具有关联的回滚日志。使用sqlite3_close()关闭数据库连接时,将自动删除用于存储TEMP表的临时数据库文件。

TEMP数据库文件与使用ATTACH语句添加的辅助数据库文件非常相似,尽管具有一些特殊属性。关闭数据库连接后,TEMP数据库始终会自动删除 。TEMP数据库始终使用 sync = OFFjournal_mode = PERSIST PRAGMA设置。并且,TEMP数据库不能与DETACH一起使用,其他进程也不能附加TEMP数据库。

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

2.7。视图和子查询的实现

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

通常必须实现IN运算符右侧的子查询。例如:

SELECT * FROM ex1在哪里ex1.a IN(SELECT b来自ex2);

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

为了避免创建临时表,查询可能会被重写如下:

SELECT * FROM ex1 WHERE EXISTS(SELECT 1 FROM ex2 WHERE ex2.b = ex1.a);

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

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

SELECT * FROM ex1 WHERE a IN(1,2,3);

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

SELECT * FROM ex1 WHERE a IN(SELECT 1 UNION ALL
                              选择2 UNION ALL
                              选择3);

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

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

SELECT * FROM ex1 JOIN(SELECT b FROM ex2)as t ON tb = ex1.a;

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

选择ex1。*,ex2.b从ex1联接ex2到ex2.b = ex1.a;

更复杂的查询可能或可能无法采用查询拼合来避免使用临时表。查询是否可以扁平化取决于诸如子查询或外部查询是否包含聚合函数,ORDER BY或GROUP BY子句,LIMIT子句等因素。查询何时可以扁平化和不能扁平化的规则非常复杂,超出了本文档的范围。

2.8。瞬态指数

SQLite可以使用瞬态索引来实现SQL语言功能,例如:

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

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命令 创建的临时文件仅在命令本身存在期间存在。临时文件的大小将不大于原始数据库。

3. SQLITE_TEMP_STORE编译时参数和语法

与事务控制关联的临时文件(即回滚日志,超级日志,预写日志(WAL)文件和共享内存文件)始终写入磁盘。但是其他类型的临时文件可能只存储在内存中,而不会写入磁盘。除回滚,超级和语句日志以外的临时文件是否写入磁盘或仅存储在内存中取决于SQLITE_TEMP_STORE编译时参数, temp_store pragma和临时文件的大小。

所述SQLITE_TEMP_STORE编译时间参数是一个的#define,其值是0和3之间的整数。SQLITE_TEMP_STORE编译时参数的含义 如下:

  1. 无论temp_store pragma的设置如何,临时文件总是存储在磁盘上。
  2. 默认情况下,临时文件存储在磁盘上,但是可以由temp_store pragma覆盖。
  3. 默认情况下,临时文件存储在内存中,但是可以由temp_store pragma覆盖。
  4. 无论temp_store pragma的设置如何,临时文件总是存储在内存中。

SQLITE_TEMP_STORE编译时参数 的默认值为1,这意味着将临时文件存储在磁盘上,但提供了使用temp_store pragma覆盖行为的选项。

TEMP_STORE编译具有一个整数值,这也影响在哪里存储临时文件的决定。temp_store pragma的值具有以下含义:

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

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

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

4.其他临时文件优化

SQLite使用最近读取和写入的数据库页面的页面缓存。该页面缓存不仅用于主数据库文件,还用于存储在临时文件中的临时索引和表。如果SQLite需要使用临时索引或表,并且将SQLITE_TEMP_STORE编译时参数和temp_store编译参数 设置为在磁盘上存储临时表和索引,则该信息最初仍会存储在页面缓存中的内存中。在页面高速缓存已满之前,不会打开临时文件,也不会真正将信息写入磁盘。

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

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

5.临时文件存储位置

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

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

  1. PRAGMA temp_store_directorysqlite3_temp_directory全局变量 设置的目录
  2. SQLITE_TMPDIR环境变量
  3. TMPDIR环境变量
  4. / var / tmp
  5. / usr / tmp
  6. / tmp
  7. 当前工作目录(“。”)
发现上面的第一个存在并且设置了写入和执行位。最后的“。” 对于在没有可用的标准临时文件位置的chroot监狱中使用SQLite的某些应用程序,回退很重要。

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

  1. PRAGMA temp_store_directorysqlite3_temp_directory全局变量 设置的文件夹
  2. GetTempPath()系统界面返回的文件夹。
在这种情况下,尽管可能是GetTempPath()系统调用,但SQLite本身并不关注环境变量。对于CYGWIN版本,搜索算法是不同的。检查源代码以获取详细信息。