Go 服务器中的高性能 SQLite 读取
Source: Dev.to
工作负载假设
这些建议基于以下前提:
- 读取占主导(写入很少或离线)
- 单个服务器进程拥有数据库
- 多个 goroutine 并发发起
SELECT - 数据库大小基本能放入 RAM 或操作系统缓存
- 对断电后的持久性 并非关键
如果这些假设发生变化,下面的一些权衡需要重新考虑。
1. 使用 WAL 模式(不可协商)
PRAGMA journal_mode = WAL;
为什么重要
- 读取永不阻塞写入
- 读取永不阻塞其他读取
- 读取时不触碰主数据库文件
- 页面从 WAL + DB 顺序读取
对于读取密集的工作负载,WAL 实际上把 SQLite 变成了 无锁读取引擎。
2. 设置 synchronous = NORMAL
PRAGMA synchronous = NORMAL;
在 WAL 模式下这是最佳平衡点:
- 事务仍保持原子性和一致性
- 每次提交不再额外执行
fsync - 磁盘刷新次数数量级下降
对读取密集的系统来说,延迟和吞吐量比突发断电时的持久性更重要。
3. 大幅增加页面缓存
PRAGMA cache_size = -65536; -- ~64 MiB per connection
- 负值表示 千字节,而不是页面数
- 缓存是每个连接独立的
- 更大的缓存降低页面错误和 B‑tree 遍历开销
更大的缓存直接转化为更少的磁盘读取和更快的扫描。
4. 启用内存映射 I/O(巨大的收益)
PRAGMA mmap_size = 20000000000; -- 20 GiB (or larger than DB)
内存映射 I/O 让 OS 页面缓存 完成大部分工作:
- 每页不再调用
read()系统调用 - 内核自动进行预读(readahead)
- 全表扫描速度显著提升
如果数据库能放入 RAM,这会把 SQLite 的读取速度提升到 接近内存速度。
经验法则: 将 mmap_size 设置为大于数据库文件的大小。
5. 将临时对象保存在内存中
PRAGMA temp_store = MEMORY;
避免以下操作产生磁盘 I/O:
- 排序
GROUP BY- 临时索引
- 子查询物化
对于分析型或扫描密集的查询,这可以消除一个隐蔽但代价高昂的瓶颈。
6. 使用独占锁(单进程优化)
PRAGMA locking_mode = EXCLUSIVE;
好处
- 更少的文件系统锁/解锁系统调用
- 每个查询的延迟略有下降
- 无需共享内存锁的协调
仅在没有其他进程需要访问数据库时安全。
7. 让 SQLite 使用工作线程
PRAGMA threads = 4;
启用:
- 并行扫描
- 更快的大型
SELECT - 在多核机器上更好的 CPU 利用率
与 Go 的 goroutine 并发配合得很好。
8. 像吞吐量取决于它一样建立索引(确实如此)
没有任何 PRAGMA 调优能拯救糟糕的查询。
指南
- 为每个出现在
WHERE、JOIN、ORDER BY中的列建立索引 - 避免在大表上使用
SELECT * - 使用
EXPLAIN QUERY PLAN检查执行计划
一个缺失的索引就能把 1 ms 的读取变成 200 ms 的全表扫描。
9. 保持查询规划器统计信息最新
PRAGMA optimize;
何时运行
- 模式(schema)变更后
- 大批量导入数据后
- 对长连接定期执行
确保 SQLite 选择最快的访问路径。
10. 只读模式以获得额外的安全性和速度
PRAGMA query_only = ON;
好处
- 防止意外写入
- 跳过部分写入相关的安全检查
- 在运营上更安全
当数据库在运行时真正不可变时使用。
推荐的基线配置
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA cache_size = -65536;
PRAGMA mmap_size = 20000000000;
PRAGMA temp_store = MEMORY;
PRAGMA locking_mode = EXCLUSIVE;
PRAGMA threads = 4;
PRAGMA query_only = ON;
这些 每个连接执行一次(journal_mode 除外,它是持久的)。
Go‑特定注意事项
- 使用连接池(
database/sql) - 允许大量读取连接(在 WAL 下成本低)
- 为热点路径复用预编译语句
- 避免不必要的读取序列化
在正确配置后,SQLite 在并发读取方面的扩展性极佳。
最后总结
SQLite 并不慢。配置不当的 SQLite 才慢。
通过 WAL、内存映射 I/O、适当的缓存以及合理的持久性权衡,SQLite 完全可以在单文件上轻松提供 每秒数百到数千次读取,且只需极少的内存和运维复杂度。
如果你的工作负载以读取为主且部署简单,SQLite 仍是最高效的数据库之一。

了解更多: FreeDevTools
任何反馈或贡献者都欢迎!它是在线的、开源的,随时供任何人使用。
⭐ 在 GitHub 上给它加星: freedevtools