随机数据的挑战
2026年6月7日,安德斯·墨菲探讨了SQLite性能优化问题。在上一篇文章中,探讨了 UUID4的随机性如何对插入速度产生重大影响,以及UUID7如何解决这一问题。但当面对其他具有随机特性的数据,而UUID7又无法解决问题时,该怎么办呢?将使用由 `SecureRandom` 生成的160位(20字节)随机值,类似 这篇文章 中描述的情况。对于会话令牌这类数据,可能不想使用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的方法。这些ID是20字节的,不想逐个字节进行比较,所以只取前8个字节并将其转换为长整型。在大多数情况下,不需要比较整个字节数组就能实现较好的排序效果。重要的是,这种比较是无符号的,以与SQLite保持一致。
当比较两个BLOB值时,结果由 `memcmp()` 函数决定。
注意:这可能不是对随机数据进行排序的最快方法。写博客时通常不联网(联网太容易分心),而且现在的搜索功能也不太好用。所以主要依靠离线文档,使用 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倍。
结论
希望这篇文章能让大家了解到,在处理无序数据时,对数据进行批量处理并采用预排序等优化方法的好处。完整的基准测试代码可以在 这里 找到。
感谢 Datastar Discord 上阅读本文草稿并提供反馈的每一个人。
讨论
- Hacker News