原文:
https://joecode.com/2026-08-19-sqlite3/,来源:https://www.ruanyifeng.com/blog/2026/08/weekly-issue-409.html
2026年8月19日
Raphael Bauer 博士撰写了一篇精彩的文章 ( 《PostgreSQL for Everything 》) ,阐述了使用 PostgreSQL 为企业提供强大支持的价值。我冒昧地纠正了其中的一些错误,主要是他应该选择 SQLite 😊(这主要是个玩笑,我非常喜欢 PostgreSQL,它是一项很棒的技术。)
与普遍认知相反,所有问题的答案并非 42,而是 SQLite。(好吧,也可能是 sqlite3 。)
SQLite 的寿命比你现在运行的绝大多数程序都要长。
当时人们普遍认为 SQLite 只是个玩具,一个文件,是塞进手机应用里的东西,这样就不用自己写配置解析器了。真正的应用需要的是真正的数据库,有真正的端口号、真正的守护进程,甚至还有凌晨三点的紧急页面。
依我拙见,SQLite 的强大之处源于以下三个方面:
让我们仔细看看。
SQLite 是一项略显老旧的技术。它于 2000 年首次发布,但同时也是全球部署最广泛的数据库引擎,而且优势非常明显。你的手机里有它,你的浏览器里有它,你的汽车里有它,甚至你乘坐的飞机上也有它。SQLite 的运行实例数量比其他所有数据库加起来还要多,而且这根本毫无悬念。
修复数据库系统中的漏洞需要时间。SQLite 不仅有足够的时间,而且还持续改进。其测试套件在 MC/DC 标准(航空电子软件也采用该标准)下实现了 100% 的分支覆盖率。测试代码量大约是库代码量的 500 倍。该项目承诺持续支持到 2050 年,这比贵公司的使命宣言规划周期更长远。
它也是公共领域的。不是开源软件。是公共领域软件。没有许可证,没有贡献者许可协议,没有署名条款,也不是那种靠 C 轮融资后又改变主意的供应商。
没错,SQLite 的确很老了。但它一直在默默地推出各种现代功能:窗口函数、 RETURNING 函数、严格表结构、生成列、 jsonb 。每次发布都是一个经过充分测试、向后兼容的小幅改进,这对于数据库来说,既是最不起眼的,也是最有价值的。
从某种意义上说,在本地安装 SQLite 很简单,因为你已经安装过了。它与所有主流 Linux 发行版捆绑在一起,也内置于 Python、Ruby、PHP、Go、Rust、.NET、Android 和 iOS 等编程语言中,而且无论你是否需要,它现在就安装在你的 Mac 上。
针对与生产环境完全相同的数据库运行测试,这不是测试容器的问题,而是 :memory: 问题。你的测试套件会在几微秒内为每个测试并行启动一个全新的数据库,无需 Docker 守护进程,也不会出现端口冲突。你测试的对象就是你最终交付的对象,因为它们是同一个库编译成的同一个二进制文件。
如果你想在服务器上运行 SQLite:你已经在运行了。它随操作系统一起提供了。
扩展部分是大家预期文章会变得平淡的部分,所以我们不要让它平静下来:
pgbouncer 。处理速度以纳秒而非毫秒计算。这使得 SQLite 成为目前支持最广泛的软件之一。对您而言,这意味着更少的维护工作和更多的时间为客户开发功能。
在云端运行 SQLite 完全无需点击,因为它就位于应用程序旁边的一个文件里。但这还不是全部。SQLite 可以取代你原本需要运行的一整套系统。
SQLite 自带 FTS5,这是一个内置于您已链接的库中的全文搜索引擎。它还提供分词器、前缀查询、短语查询、 NEAR 、布尔运算符、使用 BM25 进行自定义排名以及用于渲染结果的摘要/高亮显示功能。
这里有两点值得注意。首先,不存在同步问题,因为没有第二个系统。根据定义,您的搜索索引会与您的数据在同一事务中更新,并且永久有效。您遇到的所有"为什么搜索索引过期"的问题,都是由您不需要的架构造成的。
其次,它的速度快得令人惊讶。Simon Willison 的 Datasette 可以在一台小型虚拟机上免费运行,对数 GB 的 SQLite 文件进行分面全文搜索,并在几毫秒内返回结果。
FTS5 会支持多语言分析链和跨 40 个节点的分布式分片吗?不会。你们有 40 个节点吗?也没有。
更多相关内容: SQLite FTS5 文档
SQLite 对 JSON 的存储和查询提供了出色的支持。JSON 函数是内置的, -> 和 ->> 运算符的工作方式也符合预期,而且自 3.45 版本起,还新增了 jsonb ,这是一种二进制表示形式,每次访问时无需重新解析。
人们常常忽略的一点是:你可以对 JSON 进行索引。从 JSON 路径创建一个生成列,并为该生成列建立索引,这样即使你的模式中不存在某个字段,你也能快速查找它。无需模式即可写入,只需索引即可读取,一切尽在掌握。
所以,MongoDB 的优势在于:文档存储、ACID 事务、无需独立服务器、无需副本集、无需分片配置、无需 mongod ,磁盘上存储的只是一个可以复制的单个文件。那么,MongoDB 还有必要存在吗?之前有一篇不错的文章,讲述了一家大型出版机构弃用 MongoDB 的故事。值得注意的是,至今还没有人写过相反的例子。
事件、队列和持久日志的重要性与日俱增。Kafka、RabbitMQ 和 SQS 都提供了这些功能。但维护它们既繁琐又需要定制化,而且需要专门的技能,因此你必须为此聘请专业人员。
好消息:桌子没问题。
BEGIN IMMEDIATE;
UPDATE jobs SET status = 'running', worker = ?
WHERE id = (SELECT id FROM jobs WHERE status = 'pending'
ORDER BY id LIMIT 1)
RETURNING *;
COMMIT;
BEGIN IMMEDIATE 会预先获取写锁, RETURNING 会将已声明的行返回给你,并且该事务保证只有一个工作进程能够获取到该行。在 WAL 模式下,读取器永远不会阻塞,因此你的仪表板查询队列深度不会与工作进程发生冲突。
这里要坦诚地提醒一句,因为你值得了解:SQLite 只有一个写入器。它没有 SKIP LOCKED ,因为没有什么可以跳过的。并发的消费者会串行执行写入锁,如果你的入队速率真的达到每秒数万条,你就会感受到性能瓶颈。
但请注意发生了什么。在 PostgreSQL 版本中,队列是数据库中的一个表。而在这个版本中,队列不仅是数据库中的一个表,也是应用程序进程中的一个表。消息永远不会离开机器。没有消息代理,没有消费者组重新平衡,也不会出现"为什么部署期间分区分配发生了变化"的问题。
我的建议:先用 SQLite 作为队列。当它性能下降时,你会得到实际的数据,而不是凭感觉判断,这样你就可以放心地去购买 Kafka 了。你会惊讶于这需要多长时间。
时间序列数据很特殊。大量数据点快速到达,然后进行聚合、统计和汇总。
这里没有 TimescaleDB,所以我们直说吧。SQLite 提供的是:
mv 命令。删除旧数据使用 rm ,该命令以恒定时间运行,且不会执行 vacuum 操作。跨数据库查询使用 ATTACH 加上 UNION ALL 视图。这种方法虽然粗糙,但非常有效。这些专用系统确实非常出色,如果你每秒要处理一百万个数据点,就应该去用用看。大多数人说的"时间序列"指的是每天几百万行数据,这对于固态硬盘上的文件来说,也就相当于周二的数据传输量。
sqlite-vec 是一个单文件、无依赖项的扩展,可以将 SQLite 转换为向量数据库。它使用 C 语言编写,可以在任何 SQLite 运行的地方运行,包括通过 WASM 在浏览器中运行,并将向量存储在普通表中。
这正是 SQLite 的优势所在。你的词嵌入、源文档、元数据和全文索引都位于同一个文件中,因此混合搜索实际上是一个连接操作,而不是跨三个服务、采用三种不同一致性模型的分布式查询。只需一条语句,即可按租户、日期、关键词和向量相似度进行筛选,而且是事务性的。
此外,这一点比听起来更重要:你的整个 RAG 索引就是一个文件。你可以通过电子邮件发送它,可以将其放入 Docker 镜像中,甚至可以将其传输到离线的笔记本电脑上。试试用你的托管向量集群执行这些操作。
缓存至关重要。大多数应用程序都使用 Redis 来保存会话和热点数据。缓存的定义允许数据丢失,并可以从源头重新生成。
那么,为什么要为此运行第二个服务器呢?SQLite 提供了多种选择,具体取决于你想牺牲多少数据持久性:
PRAGMA journal_mode = WAL;
PRAGMA synchronous = OFF; -- it's a cache, live a little
或者完全跳过磁盘,使用 :memory: 或者使用 PRAGMA temp_store = MEMORY ,或者使用通过 file:cache?mode=memory&cache=shared 在连接之间共享的内存数据库。
Expiry 是一个列,并且有一个 DELETE ... WHERE expires_at < unixepoch() 定时删除,这正是 Redis 为你做的事情,只是距离更远,并且有它自己的驱逐策略,你需要去了解一下。
关键在于:通过本地主机向 Redis GET 请求大约需要 100 微秒,而对已缓存的页面进行 SQLite 点查询只需要 1 微秒。移除依赖项并没有导致速度变慢,反而提高了速度,因为最快的网络调用就是函数调用。
Redis 是一款优秀的软件。但它也是一个独立的进程,具有独立的故障模式、独立的内存预算、独立的安全防护机制,以及独立的成本项。
你可能会认为从文件中读取一小段数据比从数据库中读取要快。但事实并非如此,这并非个人观点,而是 SQLite 项目发布的基准测试结果,其标题直截了当地写道:* 比文件系统快 35%* 。
对于小于约 100KB 的数据块,SQLite 的读写速度比磁盘上的单个文件更快,并且占用空间也少约 20%。这是因为文件系统对每个数据块都收取一次 open() 、一次 close() 调用和一次目录遍历的费用,而 SQLite 只收取一个已打开文件句柄和一次 B 树查找的费用。
你还可以免费获得:原子多 blob 更新、崩溃时不会进行部分写入、不会出现文件名转义错误、不会出现"当目录有 400 万个条目时会发生什么"的问题、不会因为 inode 计数而导致 rsync 花费 6 个小时,以及一个只有一个文件的备份故事。
将有效负载存储在 BLOB 列中,如果需要更精细的操作,可以使用更紧凑的序列化方式,然后在客户端进行反序列化。SQLite 团队自己也认为 SQLite 比 fopen() 更优秀,而且他们将其视为设计目标,并非玩笑。
在 SQL 中使用递归查询处理分层数据是可行的,但从历史上看,读取、维护和调试这些数据都非常痛苦。
SQLite 完全支持递归 CTE,其相关文档堪称业内顶尖的技术文档之一。闭包表、物化路径和邻接表都能很好地工作。由于没有 LTREE ,物化路径由一个 TEXT 列和一个 GLOB 索引构成,这种方式不够优雅,速度也大致相同。
对于实际的图论工作, simple-graph 仅用几百行 SQL 代码就在普通的 SQLite 表之上实现了一个属性图。节点、边、遍历。
这条原则在这里比在其他任何地方都更适用:你的图可能有上万个节点。上万个节点可以装进 L3 缓存。你不需要 Neo4j。你只需要一个索引和一杯咖啡。
如今大多数"微服务"都是:一个模型、一个查询和 JSON 输出。
SQLite 使用 json_object() 和 json_group_array() 将任何查询转换为 JSON。这样一来,序列化层就消失了。
但 SQLite 的优势远不止于此,因为它运行在你的进程*内部 *。微服务并非被存储过程取代,而是被函数调用取代。无需部署服务,无需健康检查,无需重试逻辑,无需熔断器,无需关联分布式跟踪,也无需处理受网络抖动影响的 p99 数据。
Datasette 将概念验证推向了极致:只需指向一个 SQLite 文件,即可获得 JSON API、Web 用户界面、分面搜索以及插件生态系统,无需编写任何代码。Litestream 则负责数据持久化。这套生产级数据服务仅需两个二进制文件和一个文件即可完成。
凡事有利有弊,我不会否认弊端是不存在的。但在这个行业里,仅仅为了在查询前增加一次网络跳转而存在的服务数量并不少。
SQLite 文档本身就包含一个用递归公共表表达式编写的 Mandelbrot 集渲染器。在手册中。作为查询语法的示例。顺便提一下。
人们还用纯 SQLite CTE 实现了康威生命游戏、数独求解器和迷宫生成器。甚至还有国际象棋引擎。有人甚至在查询中实现了 Doom 的火焰特效。
太疯狂了。或许不必太当真。但你不得不佩服一个官方文档里都包含分形的数据库。
以上列表并不完整。SQLite 是一款非常灵活的软件,它可以加载扩展程序,而且很可能总能找到适合您即将安装的服务器用途的扩展程序。
原论点说得对,而 SQLite 更胜一筹:简洁性才能让你快速行动。你的技术栈中的每个系统都需要部署、监控、保护、升级、备份、付费,还要向新员工解释。PostgreSQL 简化了这些工作。SQLite 则将其简化到零,因为数据库不是一个系统,而是一个文件和一个函数调用。
是的,确实存在瓶颈。一个作者,一台机器。当你达到这个瓶颈时,你自然会知道,然后你会去使用 PostgreSQL,那将是值得庆幸的一天,因为这意味着有人在用你的作品了。
在此之前,当下一个需求出现时,问问自己:SQLite 能不能做到这一点?我们真的需要那项闪亮的新技术 X 吗?
SQLite 或许并非万能,但它能解决的问题远比你想象的要多,而且它已经安装在你的电脑上了。
留言(0 条)
使用 GitHub 登录后即可在下方直接留言