小巧、快速、可靠。
三者选其二。

SQLite 中的限制

本文中提到的“限制”是指不可超过的大小或数量。我们关注的是诸如 BLOB 中的最大字节数或表中的最大列数等问题。

SQLite 最初的设计原则是在避免任意限制。当然,任何在有限内存和磁盘空间的机器上运行的程序都存在某种形式的限制。但在 SQLite 中,这些限制并不明确定义。其原则是在内存充足且使用 32 位整数可计数的情况下,它应该可以正常工作。

不幸的是,无限制的策略已被证明会产生问题。由于上限未明确定义,因此未对其进行测试,并且在将 SQLite 推向极限时经常会发现错误。出于这个原因,从大约 3.5.8 版本(2008-04-16)开始的 SQLite 版本都具有明确定义的限制,并且这些限制在 测试套件 中得到了测试。

本文定义了 SQLite 的限制以及如何为特定应用程序定制这些限制。限制的默认设置通常相当大,足以满足几乎所有应用程序的需求。某些应用程序可能希望在此处或那里增加限制,但我们预计这种需求很少见。更常见的情况是,应用程序可能希望使用更低的限制重新编译 SQLite,以避免在高级 SQL 语句生成器中出现错误时出现过度的资源利用,或者帮助阻止注入恶意 SQL 语句的攻击者。

某些限制可以在运行时通过 sqlite3_limit() 接口在每个连接的基础上进行更改,使用为该接口定义的 限制类别 之一。运行时限制专为具有多个数据库的应用程序而设计,其中某些数据库仅供内部使用,而其他数据库则可能受潜在的恶意外部代理的影响或控制。例如,Web 浏览器应用程序可能使用内部数据库来跟踪历史页面视图,但同时拥有一个或多个由从互联网下载的 JavaScript 应用程序创建和控制的单独数据库。 sqlite3_limit() 接口允许受信任代码管理的内部数据库不受限制,同时对由不受信任的外部代码创建或控制的数据库施加严格的限制,以帮助防止拒绝服务攻击。

  1. 字符串或 BLOB 的最大长度

    SQLite 中字符串或 BLOB 中的最大字节数由预处理器宏 SQLITE_MAX_LENGTH 定义。此宏的默认值为 10 亿(1 千兆或 1,000,000,000)。您可以使用类似以下的命令行选项在编译时提高或降低此值

    -DSQLITE_MAX_LENGTH=123456789

    当前的实现仅支持最大为 231-1 或 2147483647 的字符串或 BLOB 长度。某些内置函数(如 hex())可能会在此之前很久就失败。在安全性至关重要的应用程序中,最好不要尝试增加最大字符串和 BLOB 长度。实际上,您可能可以将最大字符串和 BLOB 长度降低到几百万字节的范围内,如果可能的话。

    在 SQLite 的 INSERT 和 SELECT 处理的一部分中,数据库中每行的完整内容都将编码为单个 BLOB。因此,SQLITE_MAX_LENGTH 参数也决定了行中的最大字节数。

    可以使用 sqlite3_limit(db,SQLITE_LIMIT_LENGTH,size) 接口在运行时降低最大字符串或 BLOB 长度。

  2. 最大列数

    SQLITE_MAX_COLUMN 编译时参数用于设置以下方面的上限:

    SQLITE_MAX_COLUMN 的默认设置为 2000。您可以在编译时将其更改为最大为 32767 的值。另一方面,许多经验丰富的数据库设计人员会争辩说,一个经过良好规范化的数据库永远不需要在表中超过 100 列。

    在大多数应用程序中,列数很小——只有几十列。在 SQLite 代码生成器中,有一些地方使用 O(N²) 算法,其中 N 是列数。因此,如果您将 SQLITE_MAX_COLUMN 重新定义为一个非常大的数字,并且您生成的 SQL 使用了大量的列,您可能会发现 sqlite3_prepare_v2() 运行缓慢。

    可以使用 sqlite3_limit(db,SQLITE_LIMIT_COLUMN,size) 接口在运行时降低最大列数。

  3. SQL 语句的最大长度

    SQL 语句文本中的最大字节数限制为 SQLITE_MAX_SQL_LENGTH,默认值为 1,000,000,000。

    如果 SQL 语句的长度限制为一百万字节,那么显然您将无法通过将多百万字节的字符串嵌入为 INSERT 语句内的文字来插入它们。但无论如何您也不应该这样做。使用主机 参数 来存储您的数据。准备像这样简短的 SQL 语句

    INSERT INTO tab1 VALUES(?,?,?);

    然后使用 sqlite3_bind_XXXX() 函数将您的大字符串值绑定到 SQL 语句。使用绑定消除了在字符串中转义引号字符的必要性,降低了 SQL 注入攻击的风险。它还运行得更快,因为大字符串不需要被解析或复制太多次。

    可以使用 sqlite3_limit(db,SQLITE_LIMIT_SQL_LENGTH,size) 接口在运行时降低 SQL 语句的最大长度。

  4. 联接中表的最大数量

    SQLite 不支持包含超过 64 个表的联接。此限制源于 SQLite 代码生成器在查询优化器中为每个联接表使用一个位图。

    SQLite 使用高效的 查询规划算法,因此即使是大型联接也可以 快速准备。因此,没有机制可以提高或降低联接中表数的限制。

  5. 表达式树的最大深度

    SQLite 将表达式解析为树以进行处理。在代码生成期间,SQLite 会递归地遍历此树。因此,表达式树的深度受到限制,以避免使用过多的堆栈空间。

    SQLITE_MAX_EXPR_DEPTH 参数确定表达式树的最大深度。如果该值为 0,则不会强制执行任何限制。当前实现的默认值为 1000。

    如果 SQLITE_MAX_EXPR_DEPTH 最初为正,则可以使用 sqlite3_limit(db,SQLITE_LIMIT_EXPR_DEPTH,size) 接口在运行时降低表达式树的最大深度。换句话说,如果表达式深度存在编译时限制,则可以在运行时降低表达式树的最大深度。如果在编译时将 SQLITE_MAX_EXPR_DEPTH 设置为 0(如果表达式的深度不受限制),则 sqlite3_limit(db,SQLITE_LIMIT_EXPR_DEPTH,size) 将不起作用。

  6. 函数中参数的最大数量

    SQLITE_MAX_FUNCTION_ARG 参数确定可以传递给 SQL 函数的参数的最大数量。此限制的默认值为 100。SQLite 应该可以与具有数千个参数的函数一起使用。但是,我们怀疑任何尝试调用具有多个参数的函数的人实际上是在尝试查找使用 SQLite 的系统中的安全漏洞,而不是进行有用的工作,因此出于这个原因,我们已将此参数设置得相对较低。

    函数的参数数量有时存储在带符号字符中。因此,SQLITE_MAX_FUNCTION_ARG 的硬性上限为 127。

    可以使用 sqlite3_limit(db,SQLITE_LIMIT_FUNCTION_ARG,size) 接口在运行时降低函数中参数的最大数量。

  7. 复合 SELECT 语句中项的最大数量

    复合 SELECT 语句是由 UNION、UNION ALL、EXCEPT 或 INTERSECT 运算符连接的两个或多个 SELECT 语句。我们将复合 SELECT 中的每个单独的 SELECT 语句称为“项”。

    SQLite 中的代码生成器使用递归算法处理复合 SELECT 语句。为了限制堆栈的大小,我们因此限制了复合 SELECT 中项的数量。项的最大数量是 SQLITE_MAX_COMPOUND_SELECT,默认值为 500。我们认为这是一个慷慨的分配,因为在实践中,我们几乎从未看到复合选择中项的数量超过个位数。

    可以使用 sqlite3_limit(db,SQLITE_LIMIT_COMPOUND_SELECT,size) 接口在运行时降低复合 SELECT 项的最大数量。

  8. LIKE 或 GLOB 模式的最大长度

    SQLite 默认 LIKEGLOB 实现中使用的模式匹配算法在某些病态情况下可能会表现出 O(N²) 性能(其中 N 是模式中的字符数)。为了防止来自能够指定自己的 LIKE 或 GLOB 模式的恶棍的拒绝服务攻击,LIKE 或 GLOB 模式的长度限制为 SQLITE_MAX_LIKE_PATTERN_LENGTH 字节。此限制的默认值为 50000。现代工作站可以相对快速地评估即使是 50000 字节的病态 LIKE 或 GLOB 模式。拒绝服务问题仅在模式长度达到数百万字节时才会出现。然而,由于大多数有用的 LIKE 或 GLOB 模式最多只有几十个字节长,因此偏执的应用程序开发人员可能希望将其参数降低到几百字节的范围内,如果他们知道外部用户能够生成任意模式。

    可以使用 sqlite3_limit(db,SQLITE_LIMIT_LIKE_PATTERN_LENGTH,size) 接口在运行时降低 LIKE 或 GLOB 模式的最大长度。

  9. 单个 SQL 语句中主机参数的最大数量

    主机 参数 是 SQL 语句中的占位符,可以使用 sqlite3_bind_XXXX() 接口之一填充。许多 SQL 程序员熟悉使用问号(“?”)作为主机参数。SQLite 还支持以“:”,“$”或“@”为前缀的命名主机参数,以及形式为“?123”的编号主机参数。

    SQLite 语句中的每个主机参数都分配一个编号。这些编号通常从 1 开始,并在每个新参数时增加 1。但是,当使用“?123”形式时,主机参数编号是紧跟在问号后的数字。

    SQLite 分配空间来保存从 1 到 SQL 语句中使用的最大主机参数编号之间所有主机参数。因此,包含如 ?1000000000 之类主机参数的 SQL 语句将需要千兆字节的存储空间。这很容易压垮主机机器的资源。为了防止过多的内存分配,主机参数编号的最大值为 SQLITE_MAX_VARIABLE_NUMBER,对于 3.32.0(2020-05-22)之前的 SQLite 版本,默认值为 999,对于 3.32.0 之后的 SQLite 版本,默认值为 32766。

    可以使用 sqlite3_limit(db,SQLITE_LIMIT_VARIABLE_NUMBER,size) 接口在运行时降低最大主机参数编号。

  10. 触发器递归的最大深度

    SQLite 限制触发器的递归深度,以防止涉及递归触发器的语句使用无限制的内存。

    在 SQLite 3.6.18 版本 (2009-09-11) 之前,触发器是非递归的,因此此限制毫无意义。从 3.6.18 版本开始,支持递归触发器,但必须使用 PRAGMA recursive_triggers 语句显式启用。从 3.7.0 版本 (2009-09-11) 开始,默认情况下启用递归触发器,但可以使用 PRAGMA recursive_triggers 手动禁用。只有在启用递归触发器时,SQLITE_MAX_TRIGGER_DEPTH 才有意义。

    默认的最大触发器递归深度为 1000。

  11. 附加数据库的最大数量

    ATTACH 语句是 SQLite 的扩展,它允许将两个或多个数据库关联到同一个数据库连接,并像单个数据库一样操作。同时附加的数据库数量限制为 SQLITE_MAX_ATTACHED,默认设置为 10。附加数据库的最大数量不能超过 125。

    可以使用 sqlite3_limit(db,SQLITE_LIMIT_ATTACHED,size) 接口在运行时降低附加数据库的最大数量。

  12. 数据库文件中的最大页面数

    SQLite 能够限制数据库文件的大小,以防止数据库文件增长过大并消耗过多的磁盘空间。SQLITE_MAX_PAGE_COUNT 参数是单个数据库文件中允许的最大页面数。尝试插入会导致数据库文件大于此限制的新数据将返回 SQLITE_FULL。

    SQLITE_MAX_PAGE_COUNT 的最大可能设置为 4294967294 (232-2)。从 3.45.0 版本 (2024-01-15) 开始,4294967294 也是 SQLITE_MAX_PAGE_COUNT 的默认值。当与默认的 4096 字节页面大小一起使用时,这将给出约 17.5 TB 的最大数据库大小。如果页面大小增加到 65536 字节的最大值,那么数据库文件可以增长到大约 281 TB。

    可以使用 max_page_count PRAGMA 在运行时提高或降低此限制。

  13. 表中最大行数

    表中理论上的最大行数为 264 (18446744073709551616 或约 1.8e+19)。由于 281 TB 的最大数据库大小将首先达到,因此无法达到此限制。一个 281 TB 的数据库最多可以容纳大约 2e+13 行,而且只有在没有索引且每行包含非常少的数据时才有可能。

  14. 最大数据库大小

    每个数据库都包含一个或多个“页面”。在单个数据库中,每个页面的大小相同,但不同的数据库可以具有大小为 512 到 65536 之间的 2 的幂的页面大小(包括这两个值)。数据库文件的最大大小为 4294967294 页。在 65536 字节的最大页面大小下,这转化为大约 1.4e+14 字节的最大数据库大小(281 TB 或 256 TiB 或 281474 GB 或 256,000 GiB)。

    这个特定的上限未经测试,因为开发人员无法访问能够达到此限制的硬件。但是,测试确实验证了当数据库达到底层文件系统的最大文件大小(通常远小于理论上的最大数据库大小)时以及当数据库由于磁盘空间不足而无法增长时,SQLite 的行为是正确且合理的。

  15. 架构中最大表的数量

    每个表和索引在数据库文件中至少需要一个页面。“索引”是指使用 CREATE INDEX 语句显式创建的索引或由 UNIQUE 和 PRIMARY KEY 约束创建的隐式索引。由于数据库文件中最大的页面数为 2147483646(略超过 20 亿),因此这也是架构中表和索引数量的上限。

    每当打开数据库时,都会扫描和解析整个架构,并将架构的解析树保存在内存中。这意味着数据库连接启动时间和初始内存使用量与架构的大小成正比。