在这篇文章的环境里,“限制”意味着不能超过设定的大小或者数量。我们关心的是诸如在BLOB或者表中最大字节数。

SQLite最初被设计成避免任何限制策略的数据库。当然,运行任意程序的机器都有特定大小内存和磁盘空间限制。但是在SQLite,哪些限制没有被定义。该策略是,如果有足够内存并且可以运行在32位机器上,那么它应该可以工作。

不幸的是,没有限制策略已经暴露出问题了。因为上限没有定义,它们没经过(极限)测试,当把SQLite推向极限时,漏洞(包括潜在的安全隐患)经常出现。鉴于此原因,SQLite的新版本明确定义了限制,并且那些限制作为单元测试的一部分。

本文定义了 SQLite 的限制,如何针对这些限制定制特定的应用程序。默认的限制设置通常是适当的,几乎适合于每一个应用。有一些应用程序可能需要在这里或者那里增加一个设置,但是我们估计这非常罕见。更普遍的是,应用程序可能需要重新编译SQLite以及更低的限制来避免过多的资源利用率,以及在高级SQL语句生成器上帮助阻止攻击者注入恶意SQL语句时发生错误。

在使用 sqlite3_limit() 接口的 limit categories 上,为该接口定义一些限制,可以在运行时改变每个基础的连接。应用程序设计的运行时限制多数据库,一些仅供内部使用的限制可以影响或控制潜在的敌对外部代理。举例来说,一个web浏览器应用程序可能使用一个内部的数据库来追踪历史页面浏览量,但是它有一个或很多分离的数据库,它们被创建和控制是通过 javascript 应用,这些应用都是从互联网上下载的。那么 sqlite3_limit() 接口是允许通过可信代码来管理约束内置数据库的,同时在数据库创建或控制上有着严格的限制,它会拒绝不可信的外部代码攻击服务。

1、string或者BLOB的最大长度

SQLite中string或者BLOB的最大字节数是由预处理器宏SQLITE_MAX_LENGTH定义的。这个宏的默认值是10亿,你可以在编译时使用像下面这样的命令行参数来对这个默认值进行调整:

-DSQLITE_MAX_LENGTH=123456789

在当前实现中仅支持将string或者BLOB长度上调到最大231-1 or 2147483647。并且这个时候一些内置的函数例如hex()将会调用失败。在安全敏感的应用中最好不要尝试增加string和BlOB的最大长度。实际上,如果可以的话,你可以将string和BLOB的最大长度在一定范围内降低(几百方字节)。

在SQLite的INSERT和SELECT处理时,数据库中中的每一行的所有内容都被编码成单个BLOB。所以SQLITE_MAX_LENGTH这个参数同样也定义了一行的最大字节数。string或者BLOB的最大长度可以在运行时通过sqlite3_limit(db,SQLITE_LIMIT_LENGTH,size) 方法调低。

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) 方法调低。

  • 表中的列数
  • 索引中的列数
  • 视图中的列数
  • UPDATE语句中SET子句中的项数
  • SELECT语句结果集中的列数
  • GROUP BY 或者 ORDER BY子句中的项数
  • INSERT语句中的value项数
    • SQL语句的最大长度

      一个SQL语句文本中字节数的最大值,受限于 SQLITE_MAX_SQL_LENGTH ,其默认为 1000000。你可以重新对这个限制进行定义,大到 SQLITE_MAX_LENGTH 和 1073741824 两者中较小的一个值。

      如果一个SQL语句在长度上被限制在1百万个字节以内,那么很明显你就不能够以字符串字面量的形式将几百万字节嵌入到 INSERT 语句中。不过你应该是不会那样做的。这时候针对这些数据你可以使用占位参数,像下面这样先准备好一个简短的SQL语句:

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

      然后使用 sqlite3 的_bind_XXXX() 函数来将大型的字符串值绑定到这个SQL语句。绑定的使用回避掉了要在字符串中对引号进行转义的必要, 同时降低了遭受SQL注入攻击的风险。它运行起来也更快,因为大型的字符串不必进行其它方式要进行的多次转换和复制操作。

      SQL语句的最大长度可以在运行时使用 sqlite3 的_limit(db,SQLITE_LIMIT_SQL_LENGTH,size) 接口来减小。

    • 一次连接操作中最大的表数量

      SQLite 不支持超过64个表的连接操作。此限制源于在查询优化器中,SQLite代码生成器会使用每个连接表一个位的位图,这一事实。

      SQLite 使用了一种高效的 查询规划器算法 ,因此即使是一个大型地连接操作也能被快速地被 预处理好。 所以没有任何机制来提高或者降低一次连接操作中表的数量。

 

  • 表达式树结构的最大深度

    SQLite 会将表达解析成一个数结构来进行处理。在代码生成期间,SQLite会以递归的形式遍历这个树结构。表达式树结构的深度因此被加上了限制,以此避免用掉太多的栈空间。

    SQLITE_MAX_EXPR_DEPTH 参数决定了表达式树结构的最大深度。如果其值为 0,就表示不加任何限制。目前的实现所使用的默认值是 1000。

    如果 SQLITE_MAX_EXPR_DEPTH 初始是正数,那么表达书树结构的最大深度可以在运行时使用 sqlite3 的_limit(db,SQLITE_LIMIT_EXPR_DEPTH,size) 接口来降低。换言之,如果已经在编译时对表达式深度做了限制,那么表达式树结构深度的最大值就可以在运行时被降低。如果If SQLITE_MAX_EXPR_DEPTH 的值在编译时被设置为 0(即表达式的深度不受限制),那么sqlite3 的_limit(db,SQLITE_LIMIT_EXPR_DEPTH,size) 接口就是一个无效操作。

    • 函数里惨呼是的最大数量

      SQLITE_MAX_FUNCTION_ARG 参数决定了能够被传入到一个SQL函数的参数的最大数量。这一限制的默认值为100。SQLite 应该在函拥有数以千计的参数时也能运作。不过,我们对于那些使用过多参数的人持怀疑态度,因为他们可能是在尝试找出使用了SQLite的系统中的安全漏洞,而不是做一些实用的事情, 而因为这个原有,我们已经给这个参数设置了相对而言较低的值。

      传入函数的参数数量有时会被存储在一个有符号的字符中,因此 SQLITE_MAX_FUNCTION_ARG 有一个限定死的 127 的上限。

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

 

    • 一个复合SELECT语句中段落的最大数量

      一个复合 SELECT 语句就是那种由操作符 UNION, UNION ALL, EXCEPT, 或者 INTERSECT 连接起来的两个或者更多个SQL语句。我们将一个复合SELECT中的每一个独立的SELECT语句称为一个“段落”。

      SQLite中的代码生成器会使用一种递归算法来对复合SELECT语句进行处理。对栈的大小有必要进行一下限制,我们会因为这个缘故对复合SELECT中的段落数量进行限制。段落的最大数量限制参数就是 SQLITE_MAX_COMPOUND_SELECT ,其默认值为 500。我们认为这已经是一个比较宽裕的分配方案了,因为在实际使用中很少会遇到一个复合SELECT中段落的数量超过个位数。

      复合SELECT段落的最大数量在运行时可以使用 sqlite3 的_limit(db,SQLITE_LIMIT_COMPOUND_SELECT,size) 接口来降低。

 

  • LIKE 或者 GLOB 模式的最大数量

    在特定的一些极端场景中,SQLite默认的LIKEGLOB实现中所使用的模式匹配算法会表现出 O(N²) 的性能消耗(这里的N指的是模式中字符的数量。为了避免遭受来自那些能够自己指定 LIKE 或者 GLOB 模式的人所进行的拒绝服务攻击, LIKE 或者 GLOB 模式的长度被 SQLITE_MAX_LIKE_PATTERN_LENGTH 所指定的位数值进行了限定。这个的默认值为 50000。现代的工作站能够以相对较快的速度计算出一个拥有50000位这样极端长度的 LIKE 或者 GLOB 模式。拒绝服务攻击的问题只有在模式长度达到百万个字节位数这样的程度时才会造成影响。不过因为大多数实用的 LIKE 或者 GLOB模式长度大多只有几十个字节,偏执的开发者如果知道外部用户拥有生成任意模式的能力,也许就会想要把这个参数限制到几百个这样的范围之内。

    LIKE 或者 GLOB 模式的最大长度可以在运行时使用 sqlite3 的_limit(db,SQLITE_LIMIT_LIKE_PATTERN_LENGTH,size) 接口来减小。

    • 单个SQL语句中预置参数的最大个数

      预置参数就是SQL语句中的一个占位符,将来会被使用sqlite3 其中的一个_bind_XXXX() 接口填充。许多SQL程序员对于使用问号 (“?”) 作为预置参数都很熟悉。SQLite 也支持以前置 “:”, “$”, 或者 “@”的命名作为预置参数,并且以“?123”这样的形式对预置参数进行编号。

      SQLite语句中的每一个预置参数都被分配了一个数字。该数字一般以1开始,而后对于每一个新的参数其数字加一。然而,当“?123”这样的形式被使用了的时候,预置参数的编号就会是问号后面跟着的那个数字。

      SQLite 会给从1到最大预置参数数量之间的预置参数分配空间。因此,一个包含了像 ?1000000000 这样的预置参数的SQL就会需要千兆字节的存储。这样就会很容易地超过主机的资源供应能力。为了防止这种过度了内存分配,预置参数的最大数量就要限制到 SQLITE_MAX_VARIABLE_NUMBER 这样一个固定值,其默认为 999。

      预置参数的最大数量可以在运行时使用 sqlite3 的_limit(db,SQLITE_LIMIT_VARIABLE_NUMBER,size) 接口来减小。

 

  • 触发器递归的最大深度

    SQLite 限制了触发器的递归深度,一次来阻止一个涉及到递归触发器的语句毫无限度的使用内存。

    版本 3.6.18之前的SQLite,触发器并非递归的,因此这一限制毫无意义。从版本3.6.18开始,递归触发器得到了支持,但必须使用 PRAGMA recursive_triggers 语句来明确声明启用了它。从版本3.7.0开始,递归触发器就是默认被启用了的,但可以使用 PRAGMA recursive_triggers 来手动禁用。SQLITE_MAX_TRIGGER_DEPTH 只在其启用时有效。

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

从属数据库的最大数量

ATTACH 语句是一项SQLite扩展,它能让两个或者更多个数据库与同一个数据库连接联合起来,操作起来就好像它们是一个数据库。能够被联合的数据库数量被限制到了 SQLITE_MAX_ATTACHED 这样一个常量,其默认值为10。被联合数据库的最大数量不能超过125。

被联合的数据库的最大数量可以在运行时使用 sqlite3_limit(db,SQLITE_LIMIT_ATTACHED,size) 接口来减小。

一个数据库文件中页面的最大数量

SQLite 能够限制数据库文件的大小,以阻止数据库文件变得太大而消耗过多的磁盘空间。SQLITE_MAX_PAGE_COUNT 参数,一般会被设置成 1073741823, 就是一个数据库文件中页面的最大数量。一次会造成数据库文件增长超过这个值的插入新数据的操作将会返回 SQLITE_FULL。

SQLITE_MAX_PAGE_COUNT 最大的可能设置是 214748364。当最大页面大小为 65536 时, 最大的SQLite数据库大小就大概是140太字节。最大页面总数PRAGMA 可以被用来在运行时提高或者降低这个限制。

表中数据行的最大数量

表中数据行的最大数量理论上可以到 264 (18446744073709551616 or about 1.8e+19)。这一限制是不可及的,因为数据库会首先达到140太字节的大小限制。一个140太字节大小的数据库可以容纳超过将近 1e+13 行的数据,而要容纳这么多行数据,只能在没有指数数据存在,并且每一行只包含非常少量数据的时候。

最大数据库大小

每个数据库都包含一个或多个“页”(page)。在某个单一数据库中,每个页的大小相同,但是不同的数据库拥有不同的页大小,大小介于512字节(含)和65536字节(含)之间。一个数据库文件最多包括2147483646页。每个页最大65536字节,换算可得最大数据库大小约为1.4e+14字节(140千万兆字节或128太字节或140000百万兆字节或128000吉字节)。由于开发人员没有能满足这个限制的硬件条件,所以没有对这个上限进行测试。然而,测试表明当数据库达到底层文件系统的最大文件大小(通常要比理论上的最大数据库大小小很多)并且由于磁盘空间耗尽而无法扩展时,SQLite依然能正确并稳健地运行。

模式中表的最大数量

数据库文件中的每个表和索引至少需要一个页。这里的索引可以使用CREATE INDEX 语句显式地创建,也可以通过UNIQUE和PRIMARY KEY约束隐式地创建。由于数据库文件的页的最大数量是2147483646(比20亿还多),所以模式中表和索引的数量上限也是这个。

只要数据库被打开,就会扫描和解析整个模式,并将模式的解析树存储在内存中。也就是说数据库的启动时间和初始内存使用率与模式大小成正比。

余下全文(1/3)

本文最初发表在oschina,文章内容属作者个人观点,不代表本站立场。

分享这篇文章:

请关注我们:

发表评论

电子邮件地址不会被公开。 必填项已用*标注