SQLite FAQ

/ 默认分类 / 没有评论 / 1460浏览

(1) 如何创建 AUTOINCREMENT 字段?

简短回答:声明为 INTEGER PRIMARY KEY 的列将自动递增。 更长的答案:如果您将表的一列声明为 INTEGER PRIMARY KEY,那么每当您在表的该列中插入 NULL 时,NULL 都会自动转换为一个整数,该整数比该列的最大值大 1 表中的所有其他行,如果表为空,则为1。 或者,如果最大的现有主键 9223372036854775807 正在使用中,则随机选择未使用的主键。 例如,假设您有一个这样的表:

CREATE TABLE t1(
  a INTEGER PRIMARY KEY,
  b INTEGER
);

这张表中,下面的语句

INSERT INTO t1 VALUES(NULL,123);

相当于

INSERT INTO t1 VALUES((SELECT max(a) FROM t1)+1,123);

有一个名为 sqlite3_last_insert_rowid() 的函数,它将返回最近插入操作的整数键。

请注意,整数键比插入前表中的最大键大 1。 新键在当前表中的所有键中都是唯一的,但它可能与之前从表中删除的键重叠。 要创建在表的生命周期内唯一的键,请将 AUTOINCREMENT 关键字添加到 INTEGER PRIMARY KEY 声明中。 然后选择的键将比该表中曾经存在的最大键多一个。 如果该表中先前已存在最大可能的键,则 INSERT 将失败并显示 SQLITE_FULL 错误代码。

(2) SQLite 支持哪些数据类型?

SQLite 使用动态类型。内容可以存储为 INTEGER、REAL、TEXT、BLOB 或 NULL。

(3) SQLite 允许我将字符串插入到整数类型的数据库列中!

这是一个功能,而不是一个错误。 SQLite 使用动态类型。它不强制执行数据类型约束。任何类型的数据(通常)都可以插入到任何列中。您可以将任意长度的字符串放入整数列、布尔列中的浮点数或字符列中的日期。您在 CREATE TABLE 命令中分配给列的数据类型不限制可以将哪些数据放入该列。每列都可以容纳任意长度的字符串。 (有一个例外:类型为 INTEGER PRIMARY KEY 的列可能只包含 64 位有符号整数。如果您尝试将除整数以外的任何内容放入 INTEGER PRIMARY KEY 列,则会导致错误。)

但是 SQLite 确实使用列的声明类型作为您更喜欢该格式的值的提示。因此,例如,如果一列是 INTEGER 类型并且您尝试将字符串插入该列,SQLite 将尝试将字符串转换为整数。如果可以,它会插入整数。如果不是,则插入字符串。此功能称为类型关联。

(4) 为什么SQLite不允许我在同一个表的两个不同行上使用'0'和'0.0'作为主键?

当您的主键是数字类型时会出现此问题。 将主键的数据类型更改为 TEXT,它应该可以工作。 每行必须有一个唯一的主键。 对于数字类型的列,SQLite 认为 '0' 和 '0.0' 是相同的值,因为它们在数字上相等。 (请参阅上一个问题。)因此这些值不是唯一的。

(5) 多个应用程序或同一应用程序的多个实例可以同时访问单个数据库文件吗?

多个进程可以同时打开同一个数据库。多个进程可以同时执行 SELECT。但是,任何时候只有一个进程可以对数据库进行更改。

SQLite 使用读/写锁来控制对数据库的访问。 (在不支持读写器锁的 Win95/98/ME 下,使用概率模拟代替。)但要小心:如果数据库文件保存在 NFS 文件系统上,这种锁定机制可能无法正常工作。这是因为 fcntl() 文件锁定在许多 NFS 实现中被破坏。如果多个进程可能尝试同时访问该文件,则应避免将 SQLite 数据库文件放在 NFS 上。在 Windows 上,Microsoft 的文档说,如果您没有运行 Share.exe 守护程序,则锁定可能无法在 FAT 文件系统下工作。对 Windows 有很多经验的人告诉我,网络文件的文件锁定非常有问题,而且不可靠。如果他们所说的是真的,在两台或多台 Windows 机器之间共享 SQLite 数据库可能会导致意外问题。

我们知道没有其他嵌入式 SQL 数据库引擎支持与 SQLite 一样多的并发性。 SQLite 允许多个进程同时打开数据库文件,并允许多个进程同时读取数据库。当任何进程想要写入时,它必须在更新期间锁定整个数据库文件。但这通常只需要几毫秒。其他流程只是等待作者完成然后继续他们的业务。其他嵌入式 SQL 数据库引擎通常只允许一个进程一次连接到数据库。

但是,客户端/服务器数据库引擎(例如 PostgreSQL、MySQL 或 Oracle)通常支持更高级别的并发性,并允许多个进程同时写入同一个数据库。这在客户端/服务器数据库中是可能的,因为始终有一个控制良好的服务器进程可用于协调访问。如果您的应用程序需要大量并发,那么您应该考虑使用客户端/服务器数据库。但经验表明,大多数应用程序需要的并发性比其设计者想象的要少得多。

当 SQLite 尝试访问被另一个进程锁定的文件时,默认行为是返回 SQLITE_BUSY。您可以使用 sqlite3_busy_handler() 或 sqlite3_busy_timeout() API 函数从 C 代码调整此行为。

(6) SQLite 线程安全吗?

线程是邪恶的。 避开它们。

SQLite 是线程安全的。 我们做出这一让步是因为许多用户选择忽略上一段中给出的建议。 但是为了线程安全,SQLite 必须在编译时将 SQLITE_THREADSAFE 预处理器宏设置为 1。发行版中的 Windows 和 Linux 预编译二进制文件都是以这种方式编译的。 如果您不确定您所链接的 SQLite 库是否被编译为线程安全的,您可以调用 sqlite3_threadsafe() 接口来查找。

SQLite 是线程安全的,因为它使用互斥锁来序列化对常见数据结构的访问。 但是,获取和释放这些互斥锁的工作会稍微减慢 SQLite 的速度。 因此,如果您不需要 SQLite 成为线程安全的,您应该禁用互斥锁以获得最大性能。 有关其他信息,请参阅线程模式文档。

在 Unix 下,您不应通过 fork() 系统调用将开放的 SQLite 数据库带入子进程。

(7) 如何列出 SQLite 数据库中包含的所有表/索引

如果您正在运行 sqlite3 命令行访问程序,您可以键入“.tables”以获取所有表的列表。 或者您可以键入“.schema”以查看完整的数据库架构,包括所有表和索引。 这些命令中的任何一个都可以跟一个 LIKE 模式,该模式将限制显示的表。

在 C/C++ 程序(或使用 Tcl/Ruby/Perl/Python 绑定的脚本)中,您可以通过对名为“SQLITE_SCHEMA”的特殊表执行 SELECT 来访问表和索引名称。 每个 SQLite 数据库都有一个 SQLITE_SCHEMA 表,用于定义数据库的架构。 SQLITE_SCHEMA 表如下所示:

CREATE TABLE sqlite_schema (
  type TEXT,
  name TEXT,
  tbl_name TEXT,
  rootpage INTEGER,
  sql TEXT
);

对于表,类型字段将始终是“表”,名称字段将是表的名称。因此,要获取数据库中所有表的列表,请使用以下 SELECT 命令:

SELECT name FROM sqlite_schema
WHERE type='table'
ORDER BY name;

对于索引,type 等于 'index',name 是索引的名称,tbl_name 是索引所属的表的名称。对于表和索引,sql 字段是创建表或索引的原始 CREATE TABLE 或 CREATE INDEX 语句的文本。对于自动创建的索引(用于实现 PRIMARY KEY 或 UNIQUE 约束),sql 字段为 NULL。

不能使用 UPDATE、INSERT 或 DELETE 修改 SQLITE_SCHEMA 表(除非在特殊情况下)。 SQLITE_SCHEMA 表由 CREATE TABLE、CREATE INDEX、DROP TABLE 和 DROP INDEX 等命令自动更新。

临时表不会出现在 SQLITE_SCHEMA 表中。临时表及其索引和触发器出现在另一个名为 SQLITE_TEMP_SCHEMA 的特殊表中。 SQLITE_TEMP_SCHEMA 的工作方式与 SQLITE_SCHEMA 类似,不同之处在于它仅对创建临时表的应用程序可见。要获取所有表的列表,包括永久表和临时表,可以使用类似于以下的命令:

SELECT name FROM 
   (SELECT * FROM sqlite_schema UNION ALL
    SELECT * FROM sqlite_temp_schema)
WHERE type='table'
ORDER BY name

(8) SQLite 数据库是否有任何已知的大小限制?

有关 SQLite 限制的完整讨论,请参见https://www.sqlite.org/limits.html。

(9) SQLite 中 VARCHAR 的最大大小是多少?

SQLite 不强制 VARCHAR 的长度。 你可以声明一个 VARCHAR(10) 并且 SQLite 很乐意在那里存储一个 5 亿的字符串。 它将保持所有 5 亿个字符完好无损。 您的内容永远不会被截断。 SQLite 将“VARCHAR(N)”的列类型理解为与“TEXT”相同,而不管 N 的值如何。

(10) SQLite 是否支持 BLOB 类型?

SQLite 允许您将 BLOB 数据存储在任何列中,甚至是声明为包含其他类型的列。 BLOB 甚至可以用作 PRIMARY KEY。

(11) 如何在SQLite 中的现有表中添加或删除列。 SQLite 具有有限的 ALTER TABLE 支持,您可以使用它在表的末尾添加一列或更改表的名称。 如果要对表的结构进行更复杂的更改,则必须重新创建该表。 您可以将现有数据保存到临时表,删除旧表,创建新表,然后从临时表中将数据复制回。

例如,假设您有一个名为“t1”的表,其中列名为“a”、“b”和“c”,并且您想从该表中删除列“c”。 以下步骤说明了如何做到这一点:

BEGIN TRANSACTION;
CREATE TEMPORARY TABLE t1_backup(a,b);
INSERT INTO t1_backup SELECT a,b FROM t1;
DROP TABLE t1;
CREATE TABLE t1(a,b);
INSERT INTO t1 SELECT a,b FROM t1_backup;
DROP TABLE t1_backup;
COMMIT;

(12) 我删除了很多数据,但数据库文件并没有变小。这是一个错误吗?

不会。当您从 SQLite 数据库中删除信息时,未使用的磁盘空间会添加到内部“空闲列表”中,并在您下次插入数据时重新使用。 磁盘空间不会丢失。 但它也不会返回到操作系统。

如果您删除了大量数据并希望缩小数据库文件,请运行 VACUUM 命令。 VACUUM 将从头开始重建数据库。 这将使数据库具有一个空的空闲列表和一个最小大小的文件。 但是请注意,VACUUM 可能需要一些时间才能运行,并且在运行时它最多可以使用原始文件两倍的临时磁盘空间。

使用 VACUUM 命令的替代方法是使用 auto_vacuum pragma 启用的 auto-vacuum 模式。

(13) 我可以在我的商业产品中使用 SQLite 而不支付版税吗?

是的。 SQLite 在公共领域。不对代码的任何部分提出所有权声明。你可以用它做任何你想做的事情。

(14) 如何使用包含嵌入单引号 (') 字符的字符串文字?

SQL 标准指定通过将两个单引号放在一行中来对字符串中的单引号进行转义。在这方面,SQL 的工作方式类似于 Pascal 编程语言。例子:

    INSERT INTO xyz VALUES('5 O''clock');

(15) 什么是 SQLITE_SCHEMA 错误,为什么会出现错误?

当准备好的 SQL 语句不再有效且无法执行时,将返回 SQLITE_SCHEMA 错误。 发生这种情况时,必须使用 sqlite3_prepare() API 从 SQL 重新编译该语句。 SQLITE_SCHEMA 错误只能在使用 sqlite3_prepare() 和 sqlite3_step() 接口运行 SQL 时发生。 您永远不会收到来自 sqlite3_exec() 的 SQLITE_SCHEMA 错误。 如果您使用 sqlite3_prepare_v2() 而不是 sqlite3_prepare() 准备语句,您也不会收到错误消息。

sqlite3_prepare_v2() 接口创建了一个准备好的语句,如果架构发生变化,它将自动重新编译自己。 处理 SQLITE_SCHEMA 错误的最简单方法是始终使用 sqlite3_prepare_v2() 而不是 sqlite3_prepare()。

(17) 我在编译 SQLite 时收到一些编译器警告。这不是问题吗?这不是表示代码质量差吗?

SQLite 中的质量保证是使用全覆盖测试完成的,而不是通过编译器警告或其他静态代码分析工具。换句话说,我们验证 SQLite 实际上得到了正确的答案,而不是仅仅满足文体约束。大多数 SQLite 代码库纯粹用于测试。 SQLite 测试套件运行数以万计的独立测试用例,其中许多测试用例被参数化,以便在每次发布之前运行和评估涉及数十亿 SQL 语句的数亿个测试的正确性。开发人员使用代码覆盖工具来验证通过代码的所有路径都经过测试。每当在 SQLite 中发现错误时,都会编写新的测试用例来展示该错误,以便该错误在将来不会未被发现而再次发生。

在测试期间,SQLite 库使用特殊工具编译,允许测试脚本模拟各种故障,以验证 SQLite 是否正确恢复。仔细跟踪内存分配,即使在内存分配失败后也不会发生内存泄漏。自定义 VFS 层用于模拟操作系统崩溃和电源故障,以确保事务跨这些事件是原子的。一种故意注入 I/O 错误的机制表明 SQLite 对此类故障具有弹性。 (作为实验,尝试在其他 SQL 数据库引擎上引入这些类型的错误,看看会发生什么!)

我们还在 Linux 上使用 Valgrind 运行 SQLite,并验证它没有检测到任何问题。

有人说我们应该消除所有警告,因为良性警告掩盖了未来变化中可能出现的真实警告。这是事实。但是作为回复,开发人员观察到所有警告都已经在用于 SQLite 开发的构建中得到修复(GCC、MSVC 和 clang 的各种版本)。编译器警告通常仅由 SQLite 开发人员不使用的编译器或编译时选项引起。

(18) Unicode 字符不区分大小写匹配不起作用。

SQLite 的默认配置只支持不区分大小写的 ASCII 字符比较。这样做的原因是,进行完整的 Unicode 不区分大小写的比较和大小写转换需要的表和逻辑几乎会使 SQLite 库的大小增加一倍。 SQLite 开发人员推断,任何需要完整 Unicode 大小写支持的应用程序可能已经拥有必要的表和函数,因此 SQLite 不应占用空间来复制此功能。 默认情况下,SQLite 没有提供完整的 Unicode 大小写支持,而是提供了链接外部 Unicode 比较和转换例程的能力。应用程序可以重载内置的 NOCASE 整理序列(使用 sqlite3_create_collat​​ion())和内置的 like()、upper() 和 lower() 函数(使用 sqlite3_create_function())。 SQLite 源代码包括执行这些重载的“ICU”扩展。或者,开发人员可以根据项目中已包含的自己的 Unicode 感知比较例程编写自己的重载。

(19) INSERT 真的很慢——我每秒只能做几十个 INSERT

实际上,SQLite 在普通台式计算机上每秒可以轻松执行 50,000 或更多 INSERT 语句。但它每秒只能进行几十次交易。事务速度受磁盘驱动器旋转速度的限制。一个事务通常需要磁盘盘片完整旋转两次,而在 7200RPM 的磁盘驱动器上,每秒最多只能处理 60 个事务。 事务速度受磁盘驱动器速度的限制,因为(默认情况下)SQLite 在事务完成之前实际上会等到数据真正安全地存储在磁盘表面上。这样,如果您突然断电或操作系统崩溃,您的数据仍然安全。有关详细信息,请阅读 SQLite 中的原子提交。

默认情况下,每个 INSERT 语句都是它自己的事务。但是,如果您使用 BEGIN...COMMIT 将多个 INSERT 语句括起来,那么所有插入都将组合到一个事务中。提交事务所需的时间在所有包含的插入语句中分摊,因此每个插入语句的时间大大减少。

另一种选择是运行 PRAGMA synchronous=OFF。该命令将导致 SQLite 不等待数据到达磁盘表面,这将使写入操作看起来更快。但是,如果您在交易过程中断电,您的数据库文件可能会损坏。

(20) 我不小心从我的 SQLite 数据库中删除了一些重要信息。我怎样才能恢复它?

如果您有数据库文件的备份副本,请从备份中恢复信息。 如果没有备份,恢复是非常困难的。 您可能能够在原始数据库文件的二进制转储中找到部分字符串数据。 使用特殊工具也可以恢复数字数据,但据我们所知,不存在此类工具。 SQLite 有时使用 SQLITE_SECURE_DELETE 选项编译,该选项用零覆盖所有已删除的内容。 如果是这样,那么恢复显然是不可能的。 如果您在数据被删除后运行了 VACUUM,那么恢复也是不可能的。 如果未使用 SQLITE_SECURE_DELETE 且未运行 VACUUM,则某些已删除的内容可能仍在数据库文件中,位于标记为可重用的区域中。 但是,我们知道没有任何程序或工具可以帮助您恢复这些数据。

(21) 什么是 SQLITE_CORRUPT 错误?数据库“格式错误”是什么意思?为什么我收到这个错误?

当 SQLite 在数据库文件的结构、格式或其他控制元素中检测到错误时,将返回 SQLITE_CORRUPT 错误。

SQLite 不会在没有外部帮助的情况下损坏数据库文件。如果您的应用程序在更新过程中崩溃,您的数据是安全的。即使您的操作系统崩溃或断电,数据库也是安全的。 SQLite 的抗崩溃性已经过广泛的研究和测试,并得到了数十亿用户多年实际经验的证明。

也就是说,外部程序或硬件或操作系统中的错误可以通过多种方式破坏数据库文件。有关详细信息,请参阅如何损坏 SQLite 数据库文件。

您可以使用 PRAGMA 完整性检查对数据库完整性进行彻底但耗时的测试。

您可以使用 PRAGMA quick_check 对数据库完整性进行更快但不太彻底的测试。

根据您的数据库损坏的严重程度,您可以通过使用 CLI 将架构和内容转储到文件然后重新创建来恢复某些数据。不幸的是,一旦矮胖子从墙上掉下来,一般就不可能再把他装回去了。

(22) SQLite 是否支持外键?

从 3.6.19 (2009-10-14) 版本开始,SQLite 支持外键约束。 但是默认情况下关闭外键约束的强制执行(为了向后兼容)。 要启用外键约束强制执行,请运行 PRAGMA foreign_keys=ON 或使用 -DSQLITE_DEFAULT_FOREIGN_KEYS=1 进行编译。

(23) 如果我在构建 SQLite 时使用 SQLITE_OMIT_... 编译时选项,我会收到编译器错误。

SQLITE_OMIT_... 编译时选项仅在从规范源文件构建时有效。 当您从 SQLite 合并或从预处理的源文件构建时,它们不起作用。 可以构建一个特殊的合并,该合并将与一组预定的 SQLITE_OMIT_... 选项一起使用。 可以在 SQLITE_OMIT_... 文档中找到这样做的说明。

(24) 我的 WHERE 子句表达式 column1="column1" 不起作用。它导致表的每一行都被返回,而不仅仅是 column1 具有值“column1”的行。

在 SQL 中的字符串文字周围使用单引号,而不是双引号。 这是 SQL 标准所要求的。 您的 WHERE 子句表达式应为: column1='column1' SQL 在包含特殊字符或关键字的标识符(列名或表名)周围使用双引号。 所以双引号是一种转义标识符名称的方法。 因此,当你说 column1="column1" 相当于 column1=column1 时,这显然总是正确的。

(25) SQLite 的语法图(又名“铁路”图)是如何生成的?

每个图表都是使用 Pikchr 图表语言手写的。 这些手写规范被转换为 SVG 并作为文档构建过程的一部分插入到 HTML 文件中。 SQLite 文档的许多历史版本使用不同的过程来生成语法图。 历史过程基于 Tcl/Tk,在 http://wiki.tcl-lang.org/21708 中有描述。 较新的基于 Pikchr 的语法图于 2020 年 9 月 26 日首次登陆主干。

(26) SQL 标准要求,即使约束中的一个或多个列为 NULL,也要强制执行 UNIQUE 约束,但 SQLite 不这样做。那不是bug吗?

也许您指的是 SQL92 中的以下语句: 当且仅当表中没有两行在唯一列中具有相同的非空值时,才满足唯一约束。 该陈述是模棱两可的,至少有两种可能的解释: 当且仅当表中没有两行具有相同的值并且在唯一列中没有非空值时,才满足唯一约束。 当且仅当表中没有两行在非空的唯一列的子集中具有相同的值时,才满足唯一约束。 SQLite 遵循解释 (1),PostgreSQL、MySQL、Oracle 和 Firebird 也是如此。 Informix 和 Microsoft SQL Server 确实使用解释 (2),但是我们 SQLite 开发人员认为解释 (1) 是对需求最自然的解读,我们还希望最大限度地与其他 SQL 数据库引擎以及大多数其他 数据库引擎也适用于 (1),所以这就是 SQLite 所做的。

(27) SQLite 的出口管制分类号 (ECCN) 是什么?

在仔细审查商业控制列表 (CCL) 后,我们确信核心公共域 SQLite 源代码没有被任何 ECCN 描述,因此 ECCN 应报告为 EAR99。 以上对于核心公共域 SQLite 是正确的。 如果您通过添加新代码来扩展 SQLite,或者如果您将 SQLite 与您的应用程序静态链接,则可能会在您的特定情况下更改 ECCN。

(28) 我的查询没有返回我期望的列名。这是一个错误吗?

如果结果集的列是由 AS 子句命名的,那么 SQLite 保证使用 AS 关键字右侧的标识符作为列名。 如果结果集不使用 AS 子句,那么 SQLite 可以随意命名列。 有关更多信息,请参阅 sqlite3_column_name() 文档。