经常问的问题

  1. 如何创建 AUTOINCREMENT 字段?
  2. SQLite 支持哪些数据类型?
  3. SQLite 让我可以将一个字符串插入到整数类型的数据库列中!
  4. 为什么 SQLite 不允许我在同一个表的两个不同行上使用“0”和“0.0”作为主键?
  5. 多个应用程序或同一应用程序的多个实例可以同时访问单个数据库文件吗?
  6. SQLite 是线程安全的吗?
  7. 如何列出 SQLite 数据库中包含的所有表/索引
  8. SQLite 数据库是否有任何已知的大小限制?
  9. SQLite 中 VARCHAR 的最大大小是多少?
  10. SQLite 是否支持 BLOB 类型?
  11. 如何在 SQLite 的现有表中添加、删除或重命名列?
  12. 我删除了很多数据,但数据库文件并没有变小。这是一个错误吗?
  13. 我可以在不支付版税的情况下在我的商业产品中使用 SQLite 吗?
  14. 如何使用包含嵌入式单引号 (') 字符的字符串文字?
  15. 什么是 SQLITE_SCHEMA 错误,为什么我会得到一个错误?
  16. 我在编译 SQLite 时收到一些编译器警告。这不是问题吗?这不是说明代码质量很差吗?
  17. 不区分大小写的 Unicode 字符匹配不起作用。
  18. INSERT 真的很慢——我每秒只能做几十个 INSERT
  19. 我不小心从我的 SQLite 数据库中删除了一些重要信息。我怎样才能恢复它?
  20. 什么是 SQLITE_CORRUPT 错误?数据库“格式错误”是什么意思?为什么会出现此错误?
  21. SQLite 是否支持外键?
  22. 如果在构建 SQLite 时使用 SQLITE_OMIT_... 编译时选项,我会收到编译器错误。
  23. 我的 WHERE 子句表达式column1="column1"不起作用。它会导致返回表的每一行,而不仅仅是 column1 具有值“column1”的行。
  24. SQLite 的语法图(又名“铁路”图)是如何生成的?
  25. SQL 标准要求强制执行 UNIQUE 约束,即使约束中的一个或多个列为 NULL,但 SQLite 不这样做。那不是一个错误吗?
  26. SQLite 的出口管制分类号 (ECCN) 是什么?
  27. 我的查询没有返回我期望的列名。这是一个错误吗?

(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 有很多经验的人告诉我,网络文件的文件锁定非常有问题而且不可靠。如果他们说的是真的,

我们知道没有其他嵌入式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 限制的完整讨论,请参阅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 的支持有限,您可以使用它来添加、重命名或删除列,或者更改表的名称,详见ALTER TABLE

如果要对表或其列的结构或约束进行更复杂的更改,则必须重新创建它。您可以将现有数据保存到临时表中,删除旧表,创建新表,然后将数据从临时表中复制回来。有关过程,请参阅进行 其他类型的表架构更改

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

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

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

使用 VACUUM 命令的替代方法是自动真空模式,使用 auto_vacuum pragma启用。

(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 integrity_check 对数据库完整性进行彻底但耗时的测试。

您可以使用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,在 h​​ttp://wiki.tcl-lang.org/21708 中有描述较新的基于 Pikchr 的语法图于 2020-09-26 首次登陆主干。

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

也许您指的是 SQL92 中的以下语句:
当且仅当表中没有两行在唯一列中具有相同的非空值时,才满足唯一约束。
该声明是模棱两可的,至少有两种可能的解释:
  1. 当且仅当表中没有两行具有相同的值并且在唯一列中没有非空值时,才满足唯一约束。
  2. 当且仅当表中没有两行在非空的唯一列的子集中具有相同的值时,才满足唯一约束。
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()文档。