SQLite 的 STRICT 表:一个容易忽略,却很实用的特性

利用严格类型检查,让数据错误在写入阶段就被发现。

slashslashdev·

SQLite 的 STRICT 表:一个容易忽略,却很实用的特性

SQLite 的类型系统一直和其他关系型数据库不太一样。

在 PostgreSQL 或 MySQL 中,列声明为什么类型,就只能保存对应类型的数据。而 SQLite 默认采用的是一种更宽松的策略,它会尽量帮你完成类型转换,即使转换不了,也不一定会拒绝写入。

这种灵活性让 SQLite 很容易使用,但也给数据一致性留下了一些隐患。

SQLite 3.37.0 引入的 STRICT Tables,就是为了改善这一点。

宽松的类型系统意味着什么?

先看一个简单的例子:

CREATE TABLE users (
    age INTEGER
);

很多人会认为,这一列只能保存整数。

但在普通 SQLite 表中,下面这条语句仍然可以执行成功:

INSERT INTO users VALUES ('hello');

虽然 age 声明的是 INTEGER,SQLite 仍然会把 'hello' 保存进去,最终以 TEXT 的形式存储。

如果插入的是能够转换成整数的字符串:

INSERT INTO users VALUES ('42');

SQLite 则会自动转换成整数再保存。

这种行为来自 SQLite 的 Type Affinity(类型亲和性)。字段声明的是一种"倾向",而不是绝对约束。

对于导入历史数据、兼容不同数据来源来说,这种设计非常方便;但如果数据库本身承担的是业务数据存储,过于宽松的约束就可能让一些错误长期隐藏下来。

STRICT Tables 改变了什么?

STRICT Tables 的使用方式很简单,只需要在建表语句最后加上一个关键字:

CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    age INTEGER
) STRICT;

之后,再执行下面这条语句:

INSERT INTO users VALUES (1, 'hello');

SQLite 会直接返回错误,不再允许数据写入。

这样一来,类型错误会在数据进入数据库之前就被发现,而不是等到后面的查询、计算或者业务逻辑运行时才暴露出来。

仍然保留了合理的自动转换

STRICT 并没有彻底放弃 SQLite 一贯的灵活性。

如果数据可以无损转换,SQLite 仍然会接受。例如:

INSERT INTO users VALUES (1, '42');

这里的 "42" 可以转换为整数,因此能够正常写入。

但对于无法转换的数据:

INSERT INTO users VALUES (1, 'abc');

SQLite 就会拒绝执行。

也就是说,STRICT 并不是完全禁止类型转换,而是要求最终保存的数据必须符合字段定义的类型。

STRICT 支持哪些类型?

开启 STRICT 后,可使用的类型名称只有六种:

INT
INTEGER
REAL
TEXT
BLOB
ANY

其中:

  • INT、INTEGER:整数;
  • REAL:浮点数;
  • TEXT:文本;
  • BLOB:二进制数据;
  • ANY:允许保存任意类型。

像很多数据库里常见的 VARCHAR(255)、BOOLEAN、DATE、DECIMAL 等类型,在 STRICT 表中都不能直接使用。

另外,STRICT 还有一个要求:每一列都必须显式声明类型。

例如:

CREATE TABLE users (
    name
) STRICT;

这条语句会执行失败,因为 name 没有声明数据类型。

迁移到 STRICT 需要注意什么?

STRICT 并不能直接应用到已有的表。

SQLite 目前不支持通过 ALTER TABLE 将普通表改成 STRICT。如果想使用这一特性,通常需要新建一张 STRICT 表,再将符合要求的数据迁移过去。

如果现有数据中已经存在类型不一致的情况,也需要先进行清理,否则迁移过程可能失败。

兼容性也需要考虑

STRICT Tables 是 SQLite 3.37.0 新增的功能。

如果数据库中包含 STRICT 表,那么较早版本的 SQLite 将无法识别这种表定义。因此,在需要兼容旧版本 SQLite 的环境中,应提前确认运行时版本。

对性能有影响吗?

对于大多数应用来说,STRICT 对性能的影响可以忽略不计。

STRICT 主要是在数据写入时增加了一次类型检查。如果写入的数据已经符合字段定义,SQLite 只需要完成一次简单的验证;如果数据能够无损转换,例如将字符串 "42" 转换为整数,也仍然会正常写入。

相比磁盘 I/O、索引维护或事务提交等数据库操作,这部分额外开销通常很小,很少会成为性能瓶颈。

当然,如果应用对写入性能极其敏感,或者每秒需要处理大量数据,仍然建议结合实际业务进行基准测试,再决定是否启用 STRICT。

对于绝大多数业务系统而言,STRICT 带来的数据一致性和更早发现错误的收益,通常远大于它增加的少量类型检查开销。

什么时候适合使用?

如果数据库主要保存应用的业务数据,希望尽可能保证数据质量,那么 STRICT 往往是一个不错的选择。它能够让类型错误更早暴露,也让数据库中的数据更加一致。

当然,SQLite 原本宽松的类型系统并没有因此失去价值。对于数据导入、历史数据兼容或数据清洗这类场景,允许不同类型的数据暂时存在,反而会让处理过程更加灵活。

小结

STRICT Tables 并没有改变 SQLite 的核心设计,而是在保留原有灵活性的基础上,提供了一种更加严格的数据约束方式。

它允许安全的类型转换,却会阻止明显不符合类型要求的数据进入数据库。对于大多数需要长期维护的应用来说,只需在建表时增加一个 STRICT 关键字,就能获得更可靠的数据质量和更早的错误反馈。

SQLiteDatabaseSQL
🧑‍💻

开发者周报

关注技术的新变化