2021 年 11 月 27 日,SQLite 发布了 3.37.0 版本。没有性能翻倍,没有炫目的新功能,只是在建表语句的末尾允许开发者加上一个词:STRICT。
什么意思?翻译成大白话:从这一天起,SQLite 终于学会了拒绝一件事——“把姓名填进电话号码栏”。
此时,距离它的诞生已经过去了 21 年。而它,正是你手机里每一个 App 都在用的隐形数据库。

手机里那个你不知道的”地基”
首先要纠正一个普遍的误解:SQLite 不是你能在应用商店里下载的”软件”。你手机上没有叫”SQLite”的图标。它是一种数据库引擎——躲在 App 内部,默默负责存储和管理数据。
微信的聊天记录、支付宝的交易流水、抖音的视频缓存、手机通讯录、浏览器保存的密码、地图的离线导航包……它们背后站着的,全是 SQLite。
据可靠估计,全球有超过一万亿个 SQLite 数据库在同时运行。没有任何其他数据库能接近这个数字。它是绝对的”全球第一”。
但这位冠军有一个让人难以置信的”特长”:它完全不检查你存进去的数据,类型对不对。
“年龄填’张三’?没问题,请进”
什么叫”不检查数据类型”?笔者用一个现实场景类比。
你到银行开户,柜员递来一张表。表格上有一栏叫”年龄”,一栏叫”姓名”。你把年龄填成”张三”,把姓名填成”42”——正常情况下,柜员会把表推回来:“先生,年龄得填数字,姓名得填文字。”
SQLite 默认模式下的行为,相当于柜员看了一眼,面无表情地说:“行,你写什么我收什么。年龄是’张三’?存了。姓名是’42’?也行。这是你的自由。”
换成代码就是:你建了一张表,声明”年龄”列存整数(INTEGER),“姓名”列存文本(TEXT)。然后执行:
INSERT INTO 用户表 (年龄) VALUES ('我不是数字');
在 MySQL、PostgreSQL 这些数据库里,这条语句当场报错。在 SQLite 里?执行成功。没有任何警告。你数据库的”年龄”列里,从此安居了一个叫”我不是数字”的文本值。
这不是边缘案例。2026 年 7 月 11 日,开发者 Evan Hahn 发表了一篇博客《优先使用 SQLite 的 STRICT 表》,在 Hacker News 上拿到近 200 分、89 条评论。评论区里充满了开发者分享自己”踩过这个坑”的血泪史。
图 1:STRICT 模式 vs 非 STRICT 模式行为对比

| 操作 | 非 STRICT(默认) | STRICT 模式 |
|---|---|---|
往 INTEGER 列写 'abc'(文本塞进数字列) | ✅ 接受 | ❌ 报错 |
往 INTEGER 列写 '123'(文本数字,可无损转换) | ✅ 接受 | ✅ 接受 |
列类型写成 GARBAGE(拼写错误/虚构类型) | ✅ 接受 | ❌ 报错 |
往 ANY 列写任意类型 | ✅ 接受 | ✅ 接受 |
| 建表时不写列类型 | ✅ 接受 | ❌ 报错 |
| 只允许的类型 | 无限制 | INT, INTEGER, REAL, TEXT, BLOB, ANY |
一场持续了 20 年的哲学战争
这背后不是疏忽,不是偷懒,是 SQLite 的创造者 D. Richard Hipp 深思熟虑后的设计选择。SQLite 官网有一整页叫《灵活类型的优势》,专门为”不检查类型”这件事辩护。
要理解这个选择的根源,需要回到 2000 年。彼时 Hipp 在一家海军承包商工作,需要为战舰上的系统找一个轻量级数据库。市面上的选择要么太重、要么需要服务器——在军舰这种环境里完全不现实。于是他动手写了一个。
一个关键的影响源是 TCL——Hipp 最熟悉的编程语言。TCL 是”动态类型”语言:程序员不需要提前声明变量是什么类型,万事万物都可以当字符串处理。Hipp 把这种哲学带进了 SQLite:你声明了列的类型?很好,但那只是一个建议。实际存什么进去,你决定。
此后 20 年里,围绕”灵活类型是 feature 还是 bug”,数据库圈展开了漫长的争论。
支持方(Hipp 本人和他的团队)的核心论点有三条:
第一,“我写了 35 年代码,没见过一个被类型检查拦下的 bug。” Hipp 在官方文档中写道,他开发 TCL 和 SQLite 几十年,想不起来任何一次因为缺少类型约束而导致的程序故障。他的结论是:类型检查只在 C 和 C++ 这种靠近硬件的底层语言中有用——在把所有数据都当作”值对象”传递的 SQL 引擎里,类型检查帮不上什么忙。
第二,“类型检查拦住的,全是容易发现的低级错误。” 这个论点相当锋利:把”姓名”填进”年龄”这种荒唐事确实会被拦住——但它太好发现了,随便一次测试就会暴露。真正让你 debug 三天的,是把”姓”和”名”填反了——两者都是文本,类型检查完全看不见。Hipp 认为,类型检查给了开发者一种”数据已经干净了”的虚假安全感。
第三,“灵活性能让你做别的数据库做不了的事。” 比如用一个表充当任意类型的键值存储、复用废弃列做多种用途、从 Excel 导出的脏 CSV 文件直接入库再慢慢清洗。
反对方的反驳同样有力:
“正是那些’容易发现’的错误,在百万行数据里变成了一根你也找不到的针。类型检查从来不是为了防你 debug 时能抓到的 bug——它防的是生产环境凌晨三点、日志里没有任何报错、但用户数据已经开始系统性错乱的那个时刻。”
“你说你写了 35 年代码没见过类型 bug?SQLite 本身就是用 C 写的,你在编译它的时候就在享用 C 的类型检查。你靠严格的类型系统保证 SQLite 自身不出错,却告诉我类型检查对别人不重要?”
Hacker News 评论区里有一条被反复引用的类比:“这就像用 UDP 代替 TCP——为了速度和简单性放弃了数据校验,然后自己在应用层手动加了一遍重传、排序、验证。等全加完了你发现,你只是实现了一个更差的 TCP。”
另一位评论者说得更直接:“为性能调默认值,可以接受。为正确性调默认值——让人不放心。“
STRICT 模式到底做了什么
回到 2021 年 11 月。STRICT 关键字做的事情,概括起来三件:
一、拒绝类型不匹配的写入。 往整数列塞文本?报错。往文本列塞数字?放行——因为数字可以无损转成文本。往整数列塞字符串 '123'?也放行——因为 '123' 能完美转成数字 123。STRICT 看的是”值能不能无损转换”,不只看表面类型。这一点,其实比很多严格类型数据库更聪明。
二、拒绝虚构的数据类型。 在非 STRICT 模式下,建表时把列类型写成 GARBAGE、DATETIME、JSON、UUID、BLOBB——SQLite 全盘照收,默默当成通用类型。STRICT 模式下,只认六种:INT、INTEGER、REAL、TEXT、BLOB、ANY。你手滑把 BLOB 打成 BLOBB?当场指出来。
三、需要灵活时,用 ANY。 STRICT 不是一刀切。把某一列声明为 ANY 类型,它就能接受任何数据——和默认模式一样自由。差别在于:灵活的地方由你指定,而不是所有地方默认灵活。
为什么等了 21 年?
从 2000 年到 2021 年,21 年。为什么一个看起来如此基础的检查机制,要横跨两代工程师的职业生涯才到位?
答案藏在 SQLite 的核心承诺里:向后兼容。
SQLite 的开发者有一条近乎偏执的铁律——你今天写的 SQLite 代码,十年后升级了版本也必须 100% 正常运行。这意味着,默认行为永远不能改。一改,地球上那一万亿个运行中的 SQLite 实例就可能出问题。
图 2:SQLite 类型安全演进时间线
2000 ─ SQLite 1.0 发布,灵活类型作为核心设计哲学
│
│ "列类型只是建议,不是约束"
│
2009 ─ SQLite 3.6.19:外键约束语法支持
│ 但默认关闭,需手动 PRAGMA foreign_keys = ON
│
│ 此后 12 年间,STRICT 模式提议多次被讨论
│ 但每次都被"向后兼容"的铁律挡在门外
│
2021 ─ SQLite 3.37.0:STRICT 表支持
│ 建表末尾加 STRICT 关键字,按表级选择启用
│ 没有全局开关——依然是"自己选要不要管"的哲学
│
2026 ─ Evan Hahn 发文:"请优先使用 STRICT 表"
│ HN 199 分、89 评论,争论仍在继续
三个里程碑横跨 21 年,每一次都遵循同一个原则:可以加功能,但不能改默认行为。
这并非孤例。外键约束——防止你删除了一个用户,却在订单表里留下一万条”没有主人的订单”——SQLite 早在 2009 年就支持了这个语法,但至今默认关闭。每次打开数据库连接,你都要手动执行一句:
PRAGMA foreign_keys = ON;
才能激活外键检查。原因一模一样:改默认值会破坏向后兼容。
HN 讨论区有人提出一个折中方案:像浏览器一样,在建数据库时声明 COMPAT_MODE=2026,新版本自动启用当时的推荐配置。但此提议至今未被采纳。
有一位评论者写了这样一句话:“SQLite 非常非常少改默认值,因为它们的向后兼容承诺近乎神圣。它们不希望开发者针对 SQLite 3.53 写的软件,升级到 3.54 之后因为 CREATE TABLE 突然变成了 STRICT 而全部炸掉。”
这句话精准概括了 SQLite 的两难:一边是”不断变好”的进化冲动,另一边是”绝对不变”的兼容誓言。
SQLite 的成功,恰恰因为它什么都不管
读到此处,一个反直觉的问题自然浮现:既然 SQLite 有这么多”默认不安全”的设计,为什么它反而是全球最流行的数据库?
答案就藏在它的设计哲学里。SQLite 的成功,很大程度上来自它的”什么都不管”。
不需要安装,不需要服务器,不需要配置文件。一个几百 KB 的库文件嵌入 App 就能运行。不管你的数据类型——你爱存什么存什么。不管你的外键关系——出了事你自己兜着。不管事务隔离级别——先跑起来再说。
这种极简主义的回报是:你把 SQLite 塞进手机、浏览器、IoT 传感器、路由器、智能电视、汽车中控、飞机娱乐系统时,它从不挑剔环境、从不索要资源、从不启动失败。
就像一个万能插座——什么插头都能怼进去。至于会不会短路,不归我管。
STRICT 模式的出现,意味着这个”什么都不管”了 21 年的数据库,终于承认了一个现实:当你的用户从几十个专业的 C 程序员,膨胀到全球数百万水平参差不齐的 App 开发者时,默认的”自由”正在变成默认的”风险”。
尾声
SQLite 的这段历史,放在软件工程的大坐标系里看,是整整一个行业逐渐成熟的缩影。
早期的软件面向少数专业用户,设计哲学是”给你最大的自由,出了事是你自己的问题”。今天的软件面向数十亿普通人,设计的重心从”自由”转向了”安全”和”防呆”。
STRICT 模式不是什么激动人心的技术突破——它所做的事,MySQL 和 PostgreSQL 从诞生第一天就会。但它迟到 21 年这件事本身,无声地道出了一个事实:我们今天习以为常的许多”基本功能”,是用十几年、二十年的行业积累、争论、踩坑、回溯——一点点换来的。
下次你的手机 App 在后台安安静静往 SQLite 里存数据时,可以想一想:这个在你手机里兢兢业业工作了好几千个日夜的隐形冠军,在它人生第 21 年,才学会了一个人类小孩在幼儿园就掌握的技能——
不要把鞋子放进饭碗里。
参考链接: