SQLite 通过预排序提升性能

Hacker News Top 新闻

摘要

本文展示了在将随机数据插入 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中推荐使用严格表

Hacker News Top

SQLite的严格表强制执行严格类型检查,以防止常见的数据类型错误。本文介绍了如何使用它们以及它们的优缺点。

SQLite:万能数据库解决方案

Lobsters Hottest

本文倡导将SQLite视为一种多功能且稳定的数据库解决方案,强调其在不同技术栈中替代多种其他工具的能力。