- 引言 — 世界上最安静的统治者
- 没有服务器 — 单一文件这一激进设计
- SQLite 何时能胜过客户端-服务器数据库
- 并发的核心 — WAL 模式
- 测试的秘密武器 — 内存数据库
- 浏览器中的 SQLite — WASM 开辟的新天地
- 持久性与复制 — litestream 这一优雅解法
- 用好 SQLite 的实务清单
- 结语
- 参考资料
引言 — 世界上最安静的统治者
一提到数据库,人们通常会想到 PostgreSQL、MySQL、Oracle,现在则可能是云数据仓库。然而,地球上部署最广的数据库引擎却不是其中任何一个。答案是 SQLite。
SQLite 恐怕是世界上运行次数最多的数据库。保守估计,活跃实例数量已经达到万亿(trillion)级别。原因很简单 — 它无处不在。它存在于你口袋里的 Android 手机和 iPhone 中,运行在 Chrome、Safari、Firefox 等所有主流浏览器内部,保存着桌面应用的设置与缓存,甚至用于商用客机的航电系统。可是 SQLite 却很安静。没有服务器进程,没有管理员,也没有端口号。它只是作为一个库链接进应用,默默地做着自己的工作。
本文将梳理这位安静的统治者是如何设计,才得以传播得如此之广;SQLite 究竟在什么时候会真正胜过笨重的客户端-服务器数据库;以及在生产环境中用好 SQLite 所需的实务知识。
没有服务器 — 单一文件这一激进设计
理解 SQLite 的第一把钥匙,在于它是无服务器(serverless)的 — 这和时下流行的"无服务器云"是不同的含义。这里的无服务器,指的是 SQLite 压根就没有一个单独运行的数据库服务器进程。
设想一个典型的数据库。如果你使用 PostgreSQL,某个地方就会有一个 postgres 服务器进程在运行,应用程序通过网络套接字(通常是 TCP 5432 端口)连接到该服务器,交换 SQL。服务器负责管理连接、处理身份验证、协调并发访问。这套架构强大,但也很重。你必须安装服务器、运行它、配置它、加固它的安全性,还要监控它不要挂掉。
SQLite 把这整层都去掉了。
典型的客户端-服务器数据库
+-------------+ 网络 +------------------+ +----------+
| 应用程序 | <-- 套接字 --> | 数据库服务器进程 | --> | 磁盘 |
+-------------+ +------------------+ +----------+
SQLite
+-----------------------------------+ +----------+
| 应用程序(SQLite 作为库 | --> | 单一文件 |
| 链接进来,通过函数调用直接访问) | | (.sqlite) |
+-----------------------------------+ +----------+
在 SQLite 中,整个数据库就是磁盘上的一个文件。表、索引、视图、触发器,全部都装在这一个文件里。应用程序把 SQLite 作为库链接进来,执行 SQL 不是一次网络往返,而只是一次函数调用。所谓"连接"数据库,不过是打开那个文件而已。
这种简单性正是 SQLite 能够无处不在的根本原因。因为没有服务器需要部署,安装应用的同时也就装好了数据库。备份就是复制文件,迁移到另一台机器也只需复制文件。运维负担几乎为零。
SQLite 何时能胜过客户端-服务器数据库
SQLite 官方文档里有一句名言:"SQLite 不是要和 PostgreSQL 这类数据库竞争,而是要和 fopen() 竞争。" 也就是说,SQLite 真正的替代方案不是别的数据库,而是直接打开文件进行读写。从这个角度看,SQLite 何时是最佳选择就变得清晰了。
SQLite 特别擅长的场景:
- 应用本地数据: 桌面和移动应用的设置、状态、缓存、离线数据。每个用户拥有自己的文件。这种场景下,网络数据库是杀鸡用牛刀。
- 读多写少的负载: SQLite 的读取速度非常快。如果一个网站的内容大多只是被读取,单靠 SQLite 就能撑起惊人的流量。
- 边缘与嵌入式: 物联网设备、车辆、航电系统这类无法启动数据库服务器的环境。作为库链接进去正合适。
- 替代文件格式: 与其自造一套复杂的二进制格式,不如把 SQLite 文件当作应用的存储格式来用。这样就免费获得了模式(schema)、事务和查询能力。
- 测试与原型开发: 正如后文详细说明的那样,内存 SQLite 是测试的理想之选。
反过来,SQLite 不适合的场景也同样清楚:
- 高并发写入: SQLite 会把写入串行化。如果许多客户端每秒并发写入数千次,客户端-服务器数据库会更合适。
- 多台机器的访问: 如果需要多台服务器通过网络连接到同一个数据库,基于文件的模型并不适用。
- 超大规模 + 复杂的管理功能: 如果需要精细的权限管理、复制拓扑,或大规模并行分析,专用数据库会更好。
核心的洞察是这样的:许多应用出于习惯选择了客户端-服务器数据库,但如果看实际的负载,会发现相当多的情况下 SQLite 完全够用,而且更简单。把思路从"默认用重型数据库"转变为"只在需要时才用重型数据库",系统会轻盈得多。
并发的核心 — WAL 模式
关于 SQLite 最常见的误解是"并发能力弱"。这话说对了一半,但这种印象大多基于旧的默认模式。打开 WAL(Write-Ahead Logging,预写日志)模式后,情况会大不相同。
在传统的回滚日志(rollback journal)模式下,写入进行时读取往往会被阻塞,读取进行时写入也会被阻塞,因为锁是粗粒度地作用于整个数据库文件的。
WAL 模式改变了这个结构。它不会把变更直接写入数据库主文件,而是先追加(append)到一个单独的 WAL 文件 中。这种方式的核心优势如下:
- 读取和写入互不阻塞。 即便有一个写入正在进行,多个读取也能同时进行。读取方看到的是最后一次提交的一致快照,而写入方则追加到 WAL 中。
- 提交速度快。 把变更顺序追加到 WAL 末尾,比在文件各处随机修改要快得多。
不过,SQLite 的写入依然是一次一个。即使在 WAL 模式下,同一时刻也只有一个写入者,其余的写入要排队等待。但读取则与此无关,可以并行进行。考虑到大多数 Web 应用都是"读多写少"的模式,WAL 模式的这个特性在实践中非常契合。
WAL 模式一行命令就能打开。设置一次之后,会永久保留在数据库中。
-- 启用 WAL 模式(会永久保存在数据库文件中)
PRAGMA journal_mode = WAL;
-- 在持久性与性能之间取得平衡的常见设置
PRAGMA synchronous = NORMAL;
synchronous = NORMAL 是 WAL 模式下常搭配使用的设置。相比完全的 FULL,它减少了磁盘同步的频率,速度快得多,同时仍能保证断电时数据库不会损坏(最坏情况下也只会丢失最后几个事务)。如果在生产环境中使用 SQLite,这两条 PRAGMA 几乎已经是标配。
如果想在浏览器里亲手试试真正的 SQLite,可以在 SQL 演练场中直接运行 WAL 或窗口函数。如果想体验偏分析向的 SQL,不妨再打开 DuckDB 数据分析演练场 对比着玩玩。
测试的秘密武器 — 内存数据库
SQLite 最受欢迎的实务用途之一就是测试。只要给它一个特殊的名字而不是文件路径,SQLite 就会把整个数据库建在内存里,而不是磁盘上。
import sqlite3
# 纯粹建在内存中的数据库,而非磁盘
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
conn.execute("INSERT INTO users (name) VALUES ('Alice')")
row = conn.execute("SELECT name FROM users WHERE id = 1").fetchone()
print(row) # ('Alice',)
这在测试中之所以强大,有几个原因:
- 极快。 完全没有磁盘 I/O,测试瞬间就能跑完。即便数千个测试各自新建一个干净的数据库,也毫无压力。
- 完美隔离。 如果每个测试都新建自己专属的内存数据库,测试之间从原理上就不会有状态污染。测试一结束,数据也随内存一起消失。
- 无需搭建环境。 不必为了跑测试而单独启动、再清理一个数据库服务器。这一点在 CI 环境中尤其宝贵。
需要注意的一点是,当生产环境和测试环境使用不同的数据库时会产生的微妙差异。如果生产环境用 PostgreSQL,而测试只用内存版 SQLite,可能会因为 SQL 方言的差异,导致测试通过但生产环境出问题。因此最安全的组合是生产环境也使用 SQLite,如果做不到,至少集成测试要用和生产相同的数据库来跑。
浏览器中的 SQLite — WASM 开辟的新天地
SQLite 原本的可移植性就已经很出色,而 WebAssembly 把它带到了一个全新的地方 — 浏览器内部。
SQLite 是一份写得很规整的 C 代码库,因此非常适合编译成 WASM。结果就是,现在浏览器标签页内,不需要服务器,就能跑起真正的 SQLite 引擎。不是模仿,而是货真价实的 SQLite。前面提到的 SQL 演练场 正是以这种方式运作的。
这个组合打开的可能性相当可观:
- 完全的客户端应用: 可以构建数据不离开浏览器的离线优先(offline-first)Web 应用,把用户数据以 SQLite 文件的形式保存在浏览器的持久化存储中。
- 隐私: 敏感数据无需发送到服务器,直接在浏览器里用 SQL 分析。
- 教育与工具: 教授 SQL 或探索数据的工具,可以作为纯静态站点部署,不需要任何后端基础设施。
PostgreSQL 系也有向 WASM 靠拢的趋势,但由于 SQLite 从一开始就被设计成嵌入式库,这种迁移对它来说最为自然。"把数据库装进应用里"这个 SQLite 原本的理念,就这样原封不动地延续到了浏览器这个新的应用环境中。
持久性与复制 — litestream 这一优雅解法
在生产环境的 Web 服务器上使用 SQLite 时,人们最先担心的是:"数据库只是服务器上的一个本地文件,那服务器一旦挂掉,数据会怎样?"
传统的答案是使用客户端-服务器数据库的复制功能。但 SQLite 生态里有一种更优雅的方案 — 像 litestream 这样的工具。
litestream 的想法很巧妙。还记得前面提到的 WAL 文件吗?litestream 会实时观察追加到这个 WAL 中的变更,并把它们持续流式传输到 S3 之类的对象存储。也就是说,数据库发生的每一次变更,几乎都会实时被备份到外部。
应用程序
|
v (写入)
SQLite (本地文件 + WAL)
|
v (litestream 持续观察 WAL 变更)
流式备份到对象存储 (S3 等)
|
v (故障时)
在另一台服务器上恢复到最新状态
这种方式的优点如下:
- 近乎实时的备份。 与定期快照不同,变更一发生就会立刻流向外部。即便服务器挂掉,丢失的数据也会降到最低。
- 恢复简单。 在新服务器上从对象存储恢复数据库,就能几乎复原到最后的状态。
- 便宜且简单。 不需要另外运维一整套复制服务器集群,一个廉价的对象存储就够了。
这种方式颠覆了"SQLite 持久性弱"这一成见。它在保持单文件简单性的同时,把云对象存储作为备份目标,获得了稳健的持久性。最近,生态系统还在朝着多台服务器共享 SQLite、设置读副本的方向进一步发展,就连"SQLite 只适合单机"这种印象,也在一点点改变。
用好 SQLite 的实务清单
把前面的内容压缩成面向生产环境的实务指南,大致如下:
- 打开 WAL 模式。 对 Web 应用来说,这几乎总是正确的选择。
PRAGMA journal_mode = WAL和PRAGMA synchronous = NORMAL事实上已经是标准配置。 - 记住写入是串行化的。 SQLite 会把并发写入串行化成一个。写入事务要保持简短,不要在长事务里长时间占用。
- 设置忙等待超时(busy timeout)。 与其在写锁被占用时立刻失败,不如设置
busy_timeout让它稍等片刻,这样能平滑掉瞬时的争用。 - 测试用内存数据库。 用
:memory:数据库来获得快速且隔离的测试,但要留意和生产数据库之间的方言差异。 - 用 litestream 之类的工具保证持久性。 如果在生产 Web 环境中使用,通过 WAL 流式备份来降低数据丢失的风险。
- 备份要用正确的方法,而不是复制文件。 对正在运行的数据库直接用
cp复制,可能会导致损坏。请使用 SQLite 的备份 API、VACUUM INTO,或者 litestream。
结语
SQLite 是软件工程史上一个少见的成功案例。它并不张扬,营销也很低调,却成了地球上使用最广泛的数据库。秘诀在于极致的简单。去掉服务器、把数据库变成单一文件的决定,把 SQLite 带进了手机、浏览器和飞机。
而这种简单并不是弱点。WAL 模式在很大程度上解决了并发问题,内存模式革新了测试方式,WASM 打开了浏览器这个新舞台,litestream 消解了对持久性的担忧 — SQLite 由此彻底摆脱了"玩具数据库"这个过时的印象,成为严肃生产系统的严肃选项。
下次在新项目里下意识地想"先把 PostgreSQL 跑起来再说"时,不妨先停下来问一句:"这个负载,SQLite 是不是就够了?" 很多时候,答案会出人意料地是"够了"。而这个选择会让你的系统简单、稳健得多。
参考资料
- SQLite 官方网站: https://www.sqlite.org/
- 适用场景 (When To Use SQLite): https://www.sqlite.org/whentouse.html
- WAL 模式文档: https://www.sqlite.org/wal.html
- SQLite WASM: https://sqlite.org/wasm/
- litestream: https://litestream.io/
- 内存数据库: https://www.sqlite.org/inmemorydb.html
현재 단락 (1/94)
一提到数据库,人们通常会想到 PostgreSQL、MySQL、Oracle,现在则可能是云数据仓库。然而,地球上部署最广的数据库引擎却不是其中任何一个。答案是 **SQLite**。