打破迷思:SQLite 如何胜任生产环境
SQLite in Production: Optimizing WAL Mode, Concurrency, and VFS Layers
别再被 SQLite 只是本地数据库的旧观念束缚了。随着 NVMe SSD 的普及和边缘计算的兴起,直接在应用进程中运行 SQLite 能彻底消除网络延迟,实现亚毫秒级查询。文章深入解析了如何通过开启 WAL 模式实现读写并发,利用 busy_timeout 和 IMMEDIATE 事务避免死锁,以及通过调整 cache_size 和 mmap 优化内存管理。此外,Litestream 和 LiteFS 等自定义 VFS 层让 SQLite 在云原生环境中也能实现高可用和持久化。只要配置得当,SQLite 完全能够支撑高并发生产负载。
通过在应用进程所在的同一服务器上直接运行 SQLite,你可以完全消除网络开销。
HN 评论区
77- smartmic
不幸的是,这篇文章明显是 AI 写的——这直接让内容失去了所有吸引力,让我根本不想再读下去。此外,"生产环境"和"非生产环境"的区分也值得商榷。对于评估,我推荐以下原始文章(绝非 AI 生成):
- https://sqlite.org/whentouse.html
- yladiz
我相当确定这是 AI 生成的,但无论如何,它引发了我的思考:每当我看到这类文章时,总忍不住怀疑作者是否真的在生产环境中使用过 SQLite,因为我看到的总是关于如何优化性能的建议,比如使用 WAL,却从未提及在需要担心性能之前就会遇到的那些令人烦恼的问题或障碍。我想,现在将 SQLite 用于生产环境已成风气,而且我认为这很棒的,因为它确实是一个功能强大的数据库,但在我亲自尝试之后,我觉得我永远不会在生产环境中首选它,因为它缺乏像 Postgres 这样的数据库所拥有的许多强大功能,而其中一些功能在生产环境中其实非常关键:
- 创建表后,无法通过类似 `alter column` 的语句来修改列定义。要修改列定义,你必须使用 `writable_schema` pragma 手动更新底层架构。如果操作失误,可能会导致数据库损坏。
- 列类型相当有限。在实践中这通常不是大问题,因为你可以在应用代码中部分处理,但有时还是会让人有点头疼。
- 处理架构迁移(schema migrations)的选择非常有限。你基本上要么将迁移脚本复制到服务器并在上面运行(手动或使用 Ansible 等工具),要么在应用启动时在应用内运行迁移。理想情况下,架构迁移应与应用程序分离,而不得不……
- firesteelrain
我与该建议略有分歧的地方在于 `busy_timeout` 加上 `BEGIN IMMEDIATE` 的组合。在嵌入式系统上,这并不足够;我维护着一个被数千个客户端使用的缓存服务。它通过 MQTT 摄入数据,并提供 Web 界面进行查询。在我的设计中,有一个 MQTT 线程和一个修剪(pruning)线程。MQTT 摄入线程和修剪线程最终都可能"争夺"写锁,而增加 `busy_timeout` 只会让它们等待更久才意识到争用并转向下一个任务。我们最终需要做的是添加一个应用级锁,确保同一时间只有一个写入事务在执行;`BEGIN IMMEDIATE` 虽然能防止其他读取者被阻塞,但对于其他写入者却没什么帮助。
同时,你可能会惊讶于"单租户边缘部署"在嵌入式场景中出现的频率;带 SD 卡的树莓派是嵌入式数据库的常见配置,而这恰恰处于上述问题的交汇点。
- tnodir
> PRAGMA synchronous = NORMAL;
> 在 NORMAL 模式下,数据库引擎仅在关键时刻(例如检查点期间)同步到磁盘,而不是在每个事务提交时都同步。在 WAL 模式下,这完全能防止数据库损坏;即使服务器崩溃,丢失的也只是 WAL 中未提交的事务,数据库的完整性依然完好。
不,这并不安全。
使用这个 pragma,你可能会丢失最新已提交的事务。
- graboid
作为一个非常想在生产环境中使用 SQLite 的人,我不断碰到的一个难题是如何拥有一个友好的 GUI 来与正在运行的数据库进行交互。对于我们当前的生产数据库,我可以连接 dbeaver 等工具,方便地浏览数据、查询,甚至偶尔进行修复。如果数据库只是运行在同一个 VPS 上的一个文件,这似乎会变得更加棘手。