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

1. 简介

SQLite 努力在存储内容的数据类型方面保持灵活。例如,如果表列的类型为“INTEGER”,则 SQLite 会尝试将插入到该列中的任何内容转换为整数。因此,尝试插入字符串 '123' 会导致插入整数 123。但如果内容无法无损地转换为整数,例如输入为 'xyz',则会改为插入原始字符串。有关更多信息,请参阅SQLite 中的数据类型文档。

一些开发人员欣赏 SQLite 灵活的类型规则提供的自由,并利用这种自由来获得优势。但其他开发人员则对 SQLite 公然违反规则感到震惊,并更喜欢所有其他 SQL 数据库引擎以及 SQL 标准中发现的传统严格类型系统。对于后一组开发人员,SQLite 从 3.37.0 版(2021-11-27)开始支持严格类型模式,该模式是为每个表单独启用的。

2. 严格表

CREATE TABLE语句中,如果在结束的“)”之后添加“STRICT”表选项关键字,则严格类型规则将应用于该表。STRICT 关键字会导致以下差异

  1. 每个列定义都必须为该列指定数据类型。不再允许指定没有数据类型的列。

  2. 数据类型必须是以下之一

    • INT
    • INTEGER
    • REAL
    • TEXT
    • BLOB
    • ANY

    不允许使用其他数据类型名称,尽管在 SQLite 的未来版本中可能会添加新的类型。

  3. 插入到数据类型不为 ANY 的列中的内容必须为 NULL(假设该列上没有 NOT NULL 约束)或指定的类型。SQLite 会尝试使用通常的亲和性规则(如 PostgreSQL、MySQL、SQL Server 和 Oracle 都那样做)将数据强制转换为适当的类型。如果值无法无损地转换为指定的数据类型,则会引发 SQLITE_CONSTRAINT_DATATYPE 错误。

  4. 数据类型为 ANY 的列可以接受任何类型的数据(当然,如果它们具有 NOT NULL 约束,则会拒绝 NULL 值)。在严格表中,类型为 ANY 的列不会发生任何类型强制。

  5. 作为 PRIMARY KEY 部分的列隐式为 NOT NULL。但是,即使 PRIMARY KEY 具有隐式 NOT NULL 约束,当将 NULL 值插入INTEGER PRIMARY KEY列时,NULL 会自动转换为唯一整数,使用与普通非严格表上的INTEGER PRIMARY KEY相同的规则。

  6. PRAGMA integrity_checkPRAGMA quick_check命令检查严格表中所有列的内容类型,并在出现任何错误时显示错误。

严格表的其他所有内容都与普通非严格表中的工作方式相同

3. ANY 数据类型

多年来,在单个列中承载任何类型数据的能力已被证明非常有用。为了继续支持这种能力,即使在严格表中,也引入了新的 ANY 数据类型名称。当列的数据类型为“ANY”时,这意味着可以将任何类型的数据(整数、浮点数、字符串或二进制 blob)插入到该表中,并且其值和数据类型将完全按照插入时的状态保留。据我们所知,SQLite 是唯一支持此高级功能的 SQL 数据库引擎。

ANY 的行为在严格表与普通非严格表中略有不同。在严格表中,类型为 ANY 的列始终完全保留接收到的数据。对于普通非严格表,类型为 ANY 的列会尝试将看起来像数字的字符串转换为数值,如果成功则存储数值而不是原始字符串。例如

严格普通非严格
CREATE TABLE t1(a ANY) STRICT;
INSERT INTO t1 VALUES('000123');
SELECT typeof(a), quote(a) FROM t1;
-- result: text '000123'
CREATE TABLE t1(a ANY);
INSERT INTO t1 VALUES('000123');
SELECT typeof(a), quote(a) FROM t1;
-- result: integer 123

4. 向后兼容性

CREATE TABLE 语句末尾的 STRICT 关键字仅被 SQLite 3.37.0 版(2021-11-27)及更高版本识别。如果您尝试在早期版本的 SQLite 中打开包含 STRICT 关键字的数据库,它将无法识别该关键字并报告错误(除非下文另有说明)。但除了额外的 STRICT 关键字外,数据库的基础文件格式是相同的。

因此,通常情况下,包含一个或多个严格表的数据库文件只能由 SQLite 3.37.0 或更高版本读取和写入。但是,由 SQLite 3.37.0 或更高版本创建的数据库仍然可以由早期版本的 SQLite 读取和写入,一直追溯到 3.0.0 版(2004-06-18),只要数据库不包含任何严格表或在 SQLite 的旧版本之后引入的其他功能。

STRICT 关键字仍然可以用作标识符。(它仅在语法的特定部分被视为关键字,并且 sqlite3_keyword_check(..) 不会将其识别为常规关键字。)

4.1. 在 SQLite 的早期版本中访问严格表

由于 SQL 语言解析器的一个怪癖,如果 SQLite 3.37.0 之前的版本在打开数据库文件后立即设置“PRAGMA writable_schema=ON”(在执行任何其他需要了解架构的操作之前),则它们仍然可以读取和写入严格表。PRAGMA writable_schema=ON 的功能之一是它会禁用架构解析器中的错误。这是故意的,因为拥有 PRAGMA writable_schema=ON 的一个重要原因是促进具有损坏架构的数据库文件的恢复。因此,在 writable_schema=ON 的情况下,当架构解析器到达 STRICT 关键字时,它会自言自语“我不知道如何处理这个,但到目前为止,所有内容看起来都像有效的表定义,所以我将只使用我拥有的内容。”因此,STRICT 关键字实际上被忽略了。由于严格表的其他文件格式没有发生任何变化,因此其他所有内容都将正常工作。当然,严格的类型强制不会发生,因为 SQLite 的早期版本不知道如何执行此操作。

.dump命令在CLI中设置PRAGMA writable_schema=ON,因为 .dump 旨在从损坏的数据库文件中提取尽可能多的内容。因此,如果您使用的是旧版本的 SQLite 并且您在 CLI 中打开了一个包含严格表的数据库并在执行任何其他操作之前发出“.dump”命令,您将能够在没有严格类型强制的情况下读取和写入严格表。这可能会潜在地损坏数据库,方法是允许不正确的类型进入严格表。使用更新版本的 SQLite 重新打开数据库并运行“PRAGMA quick_check”将检测并报告所有此类损坏。

5. 其他表选项

SQLite 解析器在 CREATE TABLE 语句中的最后一个闭括号之后接受以逗号分隔的表选项列表。在撰写本文时(2021-08-23),仅识别两个选项

如果有多个选项,则可以按任意顺序指定它们。为了简单起见,当前解析器会在不抱怨的情况下接受重复的选项,但这可能会在将来的版本中发生变化,因此应用程序不应依赖它。