news 2026/6/6 23:46:20

SQLite 使用 UUID 主键风险大!UUID7 能否解决排序难题?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQLite 使用 UUID 主键风险大!UUID7 能否解决排序难题?

2026 年 6 月 5 日

在数据库中,用随机 UUID 作主键很常见。不过,随机 UUID(如 UUID4)有个已知缺点,其无序性会让聚集索引产生大量额外分页操作,因为是随机将行插入 B 树,还得重新平衡。本文能帮我们直观了解这些额外分页操作带来的性能成本。虽说本文主要针对 SQLite,但随机 UUID 的问题在其他用聚集索引的数据库中同样存在。

什么是聚集索引?

聚集索引决定了表中数据行的物理存储顺序。表的数据行存于索引的叶子页,按索引键排序。所以:每个表只能有一个聚集索引(行只能按一种方式物理排序);聚集索引就是表本身,叶子节点含完整的行数据;相比之下,非聚集索引只存索引列及指向实际行数据的指针,实际行数据存于其他地方。

Rowid

每个普通的 SQLite 表都有个隐式的 64 位整数主键,叫 rowid。表的数据存于按 rowid 排序的 B 树中,这其实就是 SQLite 的聚集索引。行的物理存储顺序遵循 rowid 序列。

无 Rowid 表

SQLite 也支持无 Rowid 表。这些表没有隐式的 rowid,声明的主键会成为聚集索引。

基准测试

我们以常规的 rowid 整数主键建立性能基准,分批次插入 1000 万行数据,每次插 100 万行。

(d/q writer ["CREATE TABLE IF NOT EXISTS event(id INT PRIMARY KEY, data BLOB)"]) (dotimes [_ 100] (time (d/with-write-tx [db writer] (dotimes [_ 1000000] (d/q db ["INSERT INTO event (data) values (?)", data])))))

结果如下:

总行数时间(毫秒)
100000001208
200000001102
300000001177
400000001138
500000001086
600000001101
700000001070
800000001069
900000001079
1000000001081

大约每秒插入 100 万行。

UUID4

现在试试用 UUID4。

(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-uuid4-bytes), data])))))

结果如下:

总行数时间(毫秒)
100000002649
200000005644
300000007137
400000008352
500000009359
600000009817
7000000010490
8000000011130
9000000011668
10000000012586

哦不!速度慢了 10 - 12 倍!

性能分析

差距不小。不过,我们能进行性能分析,而非凭空猜测。下面是归一化的差异图(diffgraph)。差异图用于比较两个性能分析快照(本例中是整数主键和 UUID4 主键),并以火焰图结构显示差异。与显示绝对变化的常规差异图不同,归一化视图会调整两个比较配置文件的样本总数,使其相同。这意味着我们能以百分比形式看到相对差异,这很重要,因为配置文件运行时间不同。

颜色表示变化方向:蓝色框表示在第二个配置文件(UUID4)中,该函数花费时间比第一个(整数主键)少;红色框表示在第二个配置文件中花费时间更多。颜色强度表示该框样本数量的相对变化(自身时间增量)。从差异图能看到,在平衡树、读写操作上花费时间更多。这是因为 UUID4 无序,是随机排序的,迫使 SQLite 不断重新平衡 B 树。

UUID7

理论上,可用 UUID7 解决问题,因为 UUID7 按时间排序,消除了 UUID4 的排序问题。看看是否能改善情况。

(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-uuid7-bytes), data])))))

结果如下:

总行数时间(毫秒)
100000001372
200000001280
300000001365
400000001250
500000001256
600000001270
700000001246
800000001257
900000001245
1000000001258

速度回到较合理水平,比基准测试稍慢。这是因为 UUID 二进制主键为 16 字节,而整数主键为 8 字节。

结论

希望本文能帮你了解 SQLite 中使用 UUID 主键的陷阱及应对办法。

完整的基准测试代码可[点击此处](https://github.com/andersmurphy/clj-cookbook/tree/master/sqlite-perils-of-uuid)查看。如果你喜欢这篇文章,可能也会对[《使用 SQLite 实现 100000 TPS》](https://andersmurphy.com/2025/12/02/100000-tps-over-a-billion-rows-the-unreasonable-effectiveness-of-sqlite.html)感兴趣。

延伸阅读

  • 聚集索引
  • 聚集索引与无 Rowid 优化
  • clj-async-profiler
  • 探索火焰图
  • 差异图

感谢 [Datastar Discord](https://discord.gg/bnRNgZjgPh) 上阅读本文草稿并提供反馈的每一个人。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/6 23:42:54

cas:120550-35-8,Biotin-PEG3-PFP,生物素-三聚乙二醇-五氟苯酚酯

描述Biotin-PEG3-PFP 是一种胺反应性生物偶联试剂,由生物素、PEG3间隔臂和PFP活性酯三部分组成,用于蛋白质生物素化标记。基本信息中文名:生物素-三聚乙二醇-五氟苯酚酯 英文名:Biotin-PEG3-PFP CAS号:120550-35-8 分子…

作者头像 李华
网站建设 2026/6/6 23:42:10

开发提效神器:用快马AI一键生成阿里云盘核心上传与秒传代码

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 请生成一个具备高效文件上传功能的模块代码。核心需求:1、使用JavaScript实现文件分片功能,将大文件切割成指定大小(如2MB)的块。2、…

作者头像 李华
网站建设 2026/6/6 23:30:23

Python+Snakemake构建单细胞RNA-seq分析流水线

发散创新:用 Python Snakemake 构建可复现、可扩展的单细胞 RNA-seq 多模态分析流水线 在单细胞 RNA-seq(scRNA-seq)分析实践中,重复造轮子仍是多数实验室的常态:手动拼接 cellranger → Scanpy → Seurat → custom …

作者头像 李华