Hotdry.
systems-engineering

SQLite 单表十亿行实现 10 万 TPS:WAL 模式与 PRAGMA 优化详解

单机 SQLite 通过 WAL、索引优化和 PRAGMA 参数,在十亿行表上轻松实现 10 万 TPS,远超网络数据库,适用于高吞吐读写场景。

SQLite 作为嵌入式单文件数据库,常被视为轻量级工具,但其在高 TPS 场景下的潜力常常被低估。Anders Murphy 在 M1 Pro MacBook 上基准测试显示,使用单一 account 表(id 主键 + balance),填充 10 亿行数据后,通过 WAL 模式和特定 PRAGMA 配置,结合批处理,可实现超过 10 万 TPS 的混合读写吞吐。这证明了 SQLite 在无网络延迟、单机场景下的 “非理性有效性”,特别适合日志系统、实时分析或缓存等读多写少的高并发应用。

核心优势在于嵌入式设计:无网络开销,避免 Amdahl 定律下锁持有时间放大问题;单写者模型虽限制并发写,但允许高效批处理。基准中,Postgres 在 10ms 网络延迟 + 可序列化隔离下仅 660 TPS,而 SQLite 无网络即 44k TPS,批处理后飙升至 186k TPS(纯写),混合读写仍超 100k TPS。证据来自虚拟线程模拟高突发并发,Pareto 用户分布模拟真实热点访问(99.95% 事务集中于 0.05% 用户)。

要落地此性能,首先配置 PRAGMA 参数,建立读写分离连接池:

PRAGMA 参数清单(连接初始化时执行):

  • journal_mode = WAL:启用写前日志,实现读写并发,避免传统回滚日志锁竞争。
  • synchronous = NORMAL:平衡耐久性和性能,减少 fsync 调用(生产慎用 OFF)。
  • cache_size = 15625:约 64MB 缓存(负值 KB,正值页数 * page_size),提升热点命中。
  • page_size = 4096:匹配常见文件系统页,提升 I/O 效率(建库前设)。
  • temp_store = MEMORY:临时表 / 索引用内存,加速排序 / 聚合。
  • busy_timeout = 5000:写锁等待 5s,重试避免忙等。

连接池实践(伪代码,参考 Clojure sqlite4clj):

writer: 单连接,CPU-bound 线程池处理写。
readers: 核心数(如 8)连接池,并发读。

其次,索引与统计优化:

  • 主键 id 已自带索引;热点字段加覆盖索引,如 CREATE INDEX idx_balance ON account(balance);
  • 执行 ANALYZE; 更新查询规划器统计,确保选择器准确(大表后必跑)。
  • 避免 SELECT COUNT(*)(O (n) 扫描),用 Meta.Count 或维护影子计数。

批处理是 TPS 关键,利用单写者:

  • 动态批处理:队列积累事务,阈值提交(如 32k 批)。
  • SAVEPOINT 内嵌事务:SAVEPOINT sp; UPDATE ...; [app logic]; UPDATE ...; RELEASE sp; 失败仅回滚内层。
  • 读写分离:读用 reader 池,写异步批入 writer,避免阻塞。

避免 ORM:ORM 抽象层增 Prepare / 网络模拟开销,直接原生 SQL(如 sqlite3 或 JDBC)。基准用低级驱动,绕过 ORM 反射。

监控与风险:

  • 阈值:WAL 文件 < 主 DB 1GB;CPU < 80%;TPS 降 20% 触发 ANALYZE/VACUUM。
  • 回滚:synchronous=OFF 风险数据丢失,用 litestream 流复制备份。
  • 限界:单写者,高纯写 >10k TPS 瓶颈;无 MVCC,序列化隔离下长事务慎用。多机用 projections 或 rqlite。

落地 checklist:

  1. 建空库:PRAGMA page_size=4096; VACUUM;
  2. 填充数据:事务批 100k 行 INSERT。
  3. 配置 PRAGMA,建索引 + ANALYZE。
  4. 部署池:1 writer + N readers。
  5. 测试:虚拟线程 1M 并发,Pareto 用户,测 TPS。
  6. 生产:litestream 备份,定期 OPTIMIZE (VACUUM+ANALYZE)。

此方案参数经基准验证,适用于单机高吞吐场景,远胜网络 DB 初始锁争。

资料来源:

(正文 1256 字)

查看归档