SQLite 在廉价 VPS 上的性能表现:真实工作负载基准测试

关于何时从 SQLite 迁移到 PostgreSQL 等客户端-服务器数据库的争论通常集中在“规模”上。然而,规模往往被误解为一个二进制阈值,而不是性能权衡的光谱。为了了解 SQLite 究竟在何时达到极限,我们需要超越合成微基准测试,去观察当工作集超过可用系统内存时它的表现如何。

由 s13k 在一台廉价的 Hetzner CX23 VPS(每月 4.99 美元)上进行的最新基准测试,为 SQLite 的能力提供了实际的观察视角。通过在仅有 3.7 GB RAM 的机器上测试 6 GB 的数据库,这些测试迫使引擎触及磁盘,模拟了数据增长最终超过硬件资源的真实场景。

测试环境

为了确保结果适用于生产环境,基准测试是在一台具有以下规格的低端共享资源机器上运行的:

  • 硬件: 2 vCPU Intel Xeon Skylake @ 2.1 GHz, 3.7 GiB RAM(无 swap)。
  • 操作系统: Debian 13,使用 ext4 文件系统(noatime, scheduler=none)。
  • 软件: SQLite 3.53.1,静态链接到 C 基准测试工具中。

生产级现实配置

基准测试并没有为了追求极致速度而进行调优,而是使用了一组“生产级现实”的 pragma 设置。这种配置在性能与数据安全性之间取得了平衡:

  • journal_mode = WAL:Write-Ahead Logging 允许读取者在不阻塞写入者的情况下进行操作,反之亦然,从而实现更好的并发性。
  • synchronous = NORMAL:这是一个关键的权衡。虽然 NORMAL 并不严格符合 ACID 持久性(掉电可能会丢失 WAL 中最近的事务),但它能防止数据库损坏,并且比 FULL 快得多。对于需要绝对持久性的应用(例如支付处理),FULL 仍然是必要的选择。
  • page_size = 8192cache_size = 256 MiB
  • mmap_size = 256 MiB

性能分析:磁盘受限 vs. 缓存命中

本研究的核心在于比较“缓存”状态(数据库完全装入 RAM)与“磁盘受限”状态(数据库大于 RAM)之间的差异。

当数据库超过 RAM 时

对于 6 GB 的数据库,每一次随机读取都必须承担 I/O 惩罚。针对混合 OLTP 工作负载(70% 读取,25% 更新,5% 插入)的结果显示,吞吐量为 3,915 ops/s,p99 延迟为 710 µs,p999 延迟为 2.2 ms

Phase Throughput p50 p95 p99 p999
BULK INSERT (10M rows) 61,300/s 8 µs 58 µs 80 µs 143 µs
SELECT random PK 3,609/s 265 µs 476 µs 715 µs 2.3 ms
SELECT indexed range scan 3,477/s 290 µs 599 µs 895 µs 2.9 ms
UPDATE per-row 2,986/s 473 µs 706 µs 2.2 ms
MIXED OLTP 3,915/s 257 µs 455 µs 710 µs 2.2 ms

“引擎天花板”

当工作集减少到约 246 MB(100 万行)时,数据库可以轻松装入系统缓存中。这揭示了 SQLite 引擎在此特定硬件上的最大潜力,从而消除了磁盘 I/O 对热路径的影响。

  • 随机 PK SELECTs: 从 3.6k/s 跃升至 155.8k/s
  • 混合 OLTP: 从 3.9k/s 增加到 53.2k/s
  • 并发读取: 从 10.3k/s 增加到 104k/s

关键结论与“I/O 崩溃”

通过比较两种状态,可以发现一个残酷的现实:一旦工作集超过了缓存,随机读取的性能会瞬间崩溃,下降幅度达 43 倍。这是 SQLite 应用在扩展规模时面临的主要瓶颈:不是引擎本身,而是底层的存储介质。

然而,即使在最坏的磁盘受限场景下,性能表现依然令人印象深刻。在 5 美元的 VPS 上,约 3.9k ops/s 的吞吐量相当于每小时约 1,400 万次操作。由于尾部延迟保持在 3 ms 以下,对于绝大多数 Web 应用来说,SQLite 仍然是一个非常可行的选择。

结论

对于大多数开发者来说,SQLite 的“规模”极限远比通常认为的要宽。虽然从 RAM 移动到磁盘时性能会大幅下降,但其绝对性能底线仍然足以处理廉价硬件上的生产负载。除非你的应用需要对每一次写入都进行严格的 ACID 持久性,或者需要极高的写入并发性,否则 SQLite 通常绰绰有余。

Sources