SQLite 通过预排序提升性能
摘要
本文展示了在将随机数据插入 SQLite 之前进行预排序,可以利用 B+ 树的顺序特性并减少页分裂,从而将插入性能提升 2-3 倍。
暂无内容
查看缓存全文
缓存时间: 2026/06/30 03:33
# SQLite:通过预排序提升性能
来源:https://andersmurphy.com/2026/06/07/sqlite-improving-performance-with-pre-sort.html
2026年6月7日
---
在上一篇文章中,我们展示了UUID4 的随机性如何对插入速度产生巨大影响(https://andersmurphy.com/2026/06/05/the-perils-of-uuid-primary-keys-in-sqlite.html),以及UUID7 如何提供帮助。但是,对于那些同样具有随机特征、又不能通过UUID7 来解决问题的数据,该怎么办呢?
## 随机数据
我们将使用一个由 `SecureRandom` 生成的 160 位(20 字节)随机值,类似于这篇文章(https://neilmadden.blog/2018/08/30/moving-away-from-uuids/)中所描述的那样。为什么?因为对于会话令牌之类的东西,你可能不希望使用 UUID7(它会泄露信息,可能对你的使用场景来说熵不够大等)。这也可以代表任何主键是随机(更具体地说,是无序)的数据。
生成它们的代码如下:
``
(defn random-unguessable-uid []
(let [buffer (byte-array 20)]
(.nextBytes secure-random buffer)))
``
让我们看看它的性能如何。
``
(d/q writer
["CREATE TABLE IF NOT EXISTS event(id BLOB PRIMARY KEY, data BLOB) WITHOUT ROWID"])
(dotimes [_ 10]
(time
(d/with-write-tx [db writer]
(dotimes [_ 1000000]
(d/q db ["INSERT INTO event (id, data) values (?, ?)"
(random-unguessable-id) data])))))
``
结果:
总行数 | 耗时(毫秒)
--- | ---
1000000 | 2478
2000000 | 4927
3000000 | 6262
4000000 | 7195
5000000 | 8257
6000000 | 8704
7000000 | 9244
8000000 | 9771
9000000 | 10387
10000000 | 11103
大约每秒十万次插入。就像 UUID4 一样,速度很慢。
## 预排序
B+ 树的本质特征是有序的。顺序写入很快。随机数据会破坏这一点,导致页面抖动、页分裂和树的重平衡。这可不好受。
但是,我们已经在批量处理数据了。那么,如果在插入之前先排序会怎样?
首先,我们需要一种快速比较随机 ID 的方法。它是 20 字节,我们不想逐个字节遍历,因此只取前 8 个字节并转换为 long 类型。我们可能不需要比较整个字节数组就能获得足够好的排序。关键是这个比较要采用无符号方式以匹配 SQLite。
> 当比较两个 BLOB 值时,结果由 `memcmp()` 决定。
*注意:这可能不是排序随机数据最快的方法。我写博客时并没有联网(太容易分心了)。如今搜索也很糟糕。所以主要依赖离线文档,配合 dash(https://kapeli.com/dash)(Linux 下对应 zeal)进行模糊文本搜索。如果你知道用 Java/Clojure 对字节数组进行排序的更快/更好的方法,请告诉我!*
``
(defn bytes->long [^bytes bytes]
(-> (ByteBuffer/wrap bytes 0 8)
(ByteBuffer/.getLong 0)))
(defn byte-compare
"比较字节数组的前 8 个最高有效字节。
大端序(匹配 SQLite 的 blob 排序)。"
[a b]
(Long/compareUnsigned
(bytes->long a)
(bytes->long b)))
``
让我们看看它的性能:
``
(d/q writer
["CREATE TABLE IF NOT EXISTS event(id BLOB PRIMARY KEY, data BLOB) WITHOUT ROWID"])
(dotimes [_ 10]
(time
(d/with-write-tx [db writer]
(->> (repeatedly 1000000 random-unguessable-id)
(sort byte-compare)
(run!
(fn [id]
(d/q db ["INSERT INTO event (it, data) values (?, ?)"
id data])))))))
``
结果:
总行数 | 耗时(毫秒)
--- | ---
1000000 | 1987
2000000 | 2251
3000000 | 2296
4000000 | 2614
5000000 | 2687
6000000 | 3244
7000000 | 3118
8000000 | 3311
9000000 | 3485
10000000 | 3835
有趣!尽管有排序的开销,但批量预排序将性能提升了约 2-3 倍。
## 结论
希望这篇文章能帮助你明白,在处理无序数据时,批量处理可以开启一些有用的优化,比如预排序。
完整的基准测试代码可以在这里找到(https://github.com/andersmurphy/clj-cookbook/tree/master/sqlite-pre-sort)。
**感谢** Datastar Discord 社区(https://discord.gg/bnRNgZjgPh)的每一位成员,他们阅读了本文的草稿并给出了反馈。
### 讨论
- Hacker News(https://news.ycombinator.com/item?id=48458938)
- Reddit(https://www.reddit.com/r/programming/comments/1u10n7z/sqlite_improving_performance_with_presort/)
相似文章
SQLite 在生产环境中的应用:优化 WAL 模式、并发性和 VFS 层
深入探讨如何优化 SQLite 以用于生产环境,涵盖预写日志模式、检查点策略、并发性改进以及自定义虚拟文件系统层,以实现低延迟的应用服务器性能。
在SQLite中推荐使用严格表
SQLite的严格表强制执行严格类型检查,以防止常见的数据类型错误。本文介绍了如何使用它们以及它们的优缺点。
SQLite:万能数据库解决方案
本文倡导将SQLite视为一种多功能且稳定的数据库解决方案,强调其在不同技术栈中替代多种其他工具的能力。
@rohanpaul_ai:让 SQLite 提速 5% 就已经很厉害了。而一个名为 KISS Sorcar 的 AI 智能体在不到 8 小时内实现了 59% 的提升……
一个名为 KISS Sorcar 的 AI 智能体在不到 8 小时内、花费不到 150 美元,在 SQLite 的四个基准测试上实现了经验证的 1.59 倍几何平均加速,并通过了超过 100 万项 SQLite 测试。这凸显了编码智能体不断增强的能力。
用 10 MB 的 FST(有限状态转换器)二进制文件替换 3 GB 的 SQLite 数据库
作者描述了将 3 GB 的 SQLite 数据库替换为 10 MB 的有限状态转换器(FST)二进制文件,以优化芬兰语-英语词典工具,在保持性能的同时将内存使用量减少了 300 倍。