如何在PostgreSQL正确的 使用UUID 作为主键

如何在PostgreSQL正确的 使用UUID 作为主键

UUID 经常用作数据库表主键。它们易于生成,易于在分布式系统之间共享并保证唯一性。

考虑到 UUID 的大小,这是否是一个正确的选择值得怀疑,但通常这不是由我们决定的。

本文的重点不是“UUID 是否是主键的正确格式”,而是如何有效地使用 UUID 作为 PostgreSQL 的主键。

Postgres 数据的UUID类型

UUID 可以被视为一个字符串,并且可能很容易将它们存储为字符串。 Postgres 具有用于存储字符串的灵活数据类型: text ,并且通常用作存储 UUID 值的主键。

它是正确的数据类型吗?当然不是。

Postgres 有一个专用于 UUID 的数据类型: uuid 。 UUID 是 128 位数据类型,因此存储单个值需要 16 个字节。 text 数据类型有 1 或 4 个字节的开销加上存储实际的字符串。

这些差异在小表中并不那么重要,但一旦开始存储数十万或数百万行,就会成为问题。

我进行了一个实验,看看实践中有何不同。有两个表只有一列 - id 作为主键。第一个表使用 text ,第二个表使用 uuid

create table bank_transfer(id text primary key
);create table bank_transfer_uuid(id uuid primary key
);

我没有指定主键索引的类型,因此 Postgres 使用默认的 B 树。

然后我使用 Spring 的 JdbcTemplate 中的 batchUpdate 向每个表插入 10 000 000 行:

jdbcTemplate.batchUpdate("insert into bank_transfer (id) values (?)",new BatchPreparedStatementSetter() {@Overridepublic void setValues(PreparedStatement ps, int i) throws SQLException {ps.setString(1, UUID.randomUUID().toString());}@Overridepublic int getBatchSize() {return 10_000_000;}
});
jdbcTemplate.batchUpdate("insert into bank_transfer_uuid (id) values (?)",new BatchPreparedStatementSetter() {@Overridepublic void setValues(PreparedStatement ps, int i) throws SQLException {ps.setObject(1, UUID.randomUUID());}@Overridepublic int getBatchSize() {return 10_000_000;}});

查询表大小和索引大小:

select relname as "table", indexrelname as "index",pg_size_pretty(pg_relation_size(relid)) "table size",pg_size_pretty(pg_relation_size(indexrelid)) "index size"
from pg_stat_all_indexes
where relname not like 'pg%';
+------------------+-----------------------+----------+----------+
|table             |index                  |table size|index size|
+------------------+-----------------------+----------+----------+
|bank_transfer_uuid|bank_transfer_uuid_pkey|422 MB    |394 MB    |
|bank_transfer     |bank_transfer_pkey     |651 MB    |730 MB    |
+------------------+-----------------------+----------+----------+

使用 text 的表增大了 54%,索引大小增大了 85%。这也反映在 Postgres 用于存储这些表和索引的页数上:

select relname, relpages from pg_class 
where relname like 'bank_transfer%';
+-----------------------+--------+
|relname                |relpages|
+-----------------------+--------+
|bank_transfer          |83334   |
|bank_transfer_pkey     |85498   |
|bank_transfer_uuid     |54055   |
|bank_transfer_uuid_pkey|50463   |
+-----------------------+--------+

更大的表、索引和更多的表意味着 Postgres 必须执行插入新行和获取行的工作 - 特别是当索引大小大于可用 RAM 内存时,Postgres 必须从磁盘加载索引。

UUID 和 B 树索引

随机 UUID 不太适合 B 树索引 - 并且 B 树索引是主键唯一可用的索引类型

B 树索引最适合处理有序值 - 例如自动递增列或时间排序列。

UUID - 尽管看起来总是相似 - 有多种变体。 Java 的 UUID.randomUUID() - 返回 UUID v4 - 这是一个伪随机值。对我们来说,更有趣的是 UUID v7 - 它生成按时间排序的值。这意味着每生成一个新的UUID v7,它就有一个更大的值。这使得它非常适合 B 树索引。

要在 Java 中使用 UUID v7,我们需要一个第三方库,例如 java-uuid-generator:

<dependency><groupId>com.fasterxml.uuid</groupId><artifactId>java-uuid-generator</artifactId><version>5.0.0</version>
</dependency>

然后我们可以使用以下命令生成 UUID v7:

UUID uuid = Generators.timeBasedEpochGenerator().generate();
// 多次运行结果, 字符串长度为36
0190a48c-2ee3-7c50-b45c-d51fecb36a9a
0190a48c-8fce-7fd3-b141-1fb312f9096e
0190a48c-f36b-707d-9826-644fe72b7e02
0190a48d-e236-76b5-a03c-54f09fa27e9c

理论上,这应该可以提高执行 INSERT 语句的性能。

UUID v7 如何影响 INSERT 性能

我创建了另一个表,与 bank_transfer_uuid 完全相同,但它将仅存储使用上述库生成的 UUID v7:

create table bank_transfer_uuid_v7(id uuid primary key
);

然后,我运行 10 轮,向每个表插入 10000 行,并测量需要多长时间:

for (int i = 1; i <= 10; i++) {measure(() -> IntStream.rangeClosed(0, 10000).forEach(it -> {jdbcClient.sql("insert into bank_transfer (id) values (:id)").param("id", UUID.randomUUID().toString()).update();}));measure(() -> IntStream.rangeClosed(0, 10000).forEach(it -> {jdbcClient.sql("insert into bank_transfer_uuid (id) values (:id)").param("id", UUID.randomUUID()).update();}));measure(() -> IntStream.rangeClosed(0, 10000).forEach(it -> {jdbcClient.sql("insert into bank_transfer_uuid_v7 (id) values (:id)").param("id", Generators.timeBasedEpochGenerator().generate()).update();}));
}

结果看起来有点随机,在与常规 text 列和 uuid v4 的表的时间比较时:

+-------+-------+---------+
| text  | uuid  | uuid v7 |
+-------+-------+---------+
| 7428  | 8584  | 3398    |
| 5611  | 4966  | 3654    |
| 13849 | 10398 | 3771    |
| 6585  | 7624  | 3679    |
| 6131  | 5142  | 3861    |
| 6199  | 10336 | 3722    |
| 6764  | 6039  | 3644    |
| 9053  | 5515  | 3621    |
| 6134  | 5367  | 3706    |
| 11058 | 5551  | 3850    |
+-------+-------+---------+

但我们可以清楚地看到,插入 UUID v7 比插入常规 UUID v4 快约 2 倍。

Further reading 进一步阅读

  • UUID v7 will likely be supported natively in Postgres 17
    Postgres 17 可能会原生支持 UUID v7
  • UUID Version 7 format
    UUID 版本 7 格式
  • UUIDs are Popular, but Bad for Performance
    UUID 很流行,但对性能不利
  • https://vladmihalcea.com/uuid-database-primary-key/

概括

正如一开始提到的 - 由于 UUID 长度 - 即使进行了所有这些优化,它也不是主键的最佳类型。如果您可以选择,请查看由 Vlad Mihalcea 维护的 TSID。

但如果您必须或出于某种原因想要使用 UUID,请考虑我提到的优化。另请记住,此类优化对于大型数据集会产生影响。如果您存储数百甚至数千行,并且访问量较低,您可能不会看到应用程序性能有任何差异。但是,如果您有可能拥有大型数据集或大访问量 - 最好从一开始就这样做,因为更改主键可能是一个相当大的挑战。

Maciej Walkowiak | PostgreSQL and UUID as primary key

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/871305.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

游戏缺失steam_api64.dll的多种解决方法,分享几种靠谱的方法

在使用电脑进行游戏的过程中&#xff0c;可能会出现“找不到steam_api.dll&#xff0c;无法继续执行代码”的提示&#xff0c;导致游戏无法正常运行。对于这样的情况&#xff0c;我们需要采取一定的措施进行修复。本文将为您提供相关的解决方案。 一、找不到steam_api.dll对电脑…

python-28-零基础自学python-json存数据、读数据,及程序合并

学习内容&#xff1a;《python编程&#xff1a;从入门到实践》第二版 知识点&#xff1a; import json引入、 try-except-else return def函数、打开文件、 练习内容&#xff1a; 练习10-11&#xff1a;喜欢的数 编写一个程序&#xff0c;提示用户输入喜欢的数&#xff…

【人工智能】knn算法

目录 一、对[1.0,1.1],[1.0,1.0],[0,0],[0,0.1],[0.1,0.1],[1.1,1.1]六个点用knn进行聚类&#xff0c;并显示。 1. 未调用KNN算法前&#xff0c;绿色为未知分类 2. 调用KNN算法 3. 运行结果 二、使用knn算法分类手写数字文件 1. 第一个temp的含义 2. 第二个temp的含义 …

【ARM】使用JasperGold和Cadence IFV科普

#工作记录# 原本希望使用CCI自带的验证脚本来验证修改过后的address map decoder&#xff0c;但是发现需要使用JasperGold或者Cadence家的IFV的工具&#xff0c;我们公司没有&#xff0c;只能搜搜资料做一下科普了解&#xff0c;希望以后能用到吧。这个虽然跟ARM没啥关系不过在…

基于神经网络的分类和预测

基于神经网络的分类和预测 一、基础知识&#xff08;一&#xff09;引言&#xff08;二&#xff09;神经网络的基本概念&#xff08;1&#xff09;神经网络&#xff08;2&#xff09;神经元&#xff08;3&#xff09;常用的激活函数&#xff08;非线性映射函数&#xff09;&…

【Linux网络】IP协议{初识/报头/分片/网段划分/子网掩码/私网公网IP/认识网络世界/路由表}

文章目录 1.入门了解2.认识报头3.认识网段4.路由跳转相关指令路由 该文诸多理解参考文章&#xff1a;好文&#xff01; 1.入门了解 用户需求&#xff1a;将我的数据可靠的跨网络从A主机送到B主机 传输层TCP&#xff1a;由各种方法&#xff08;流量控制/超时重传/滑动窗口/拥塞…

RAG 召回提升相关方案分享

最近大半年时间都在做RAG的工作&#xff0c;分享一点个人探索的方向。和提升的方案。文章中会分享是如何做的&#xff0c;以及对应的效果。 核心问题 如何提升RAG的效果&#xff1f; 如何提升召回的准确率。 写在前边&#xff1a;已验证的方案 方案 优化方向 效果 备注 3.1…

iPad锁屏密码忘记怎么办?有什么方法可以解锁?

当我们在日常使用iPad时&#xff0c;偶尔可能会遇到忘记锁屏密码的尴尬情况。这时&#xff0c;不必过于担心&#xff0c;因为有多种方法可以帮助您解锁iPad。接下来&#xff0c;小编将为您详细介绍这些解决方案。 一、使用iCloud的“查找我的iPhone”功能 如果你曾经启用了“查…

SSM学习6:Spring事务

简介 事务作用&#xff1a;在数据层保障一系列的数据库操作同成功同失败Spring事务作用&#xff1a;在数据层或业务层保障一系列的数据库操作同成功同失败 public interface PlatformTransactionManager{void commit(TransactionStatus status) throws TransactionStatus ;vo…

【网络文明】关注网络安全

在这个数字化时代&#xff0c;互联网已成为我们生活中不可或缺的一部分&#xff0c;它极大地便利了我们的学习、工作、娱乐乃至日常生活。然而&#xff0c;随着网络空间的日益扩大&#xff0c;网络安全问题也日益凸显&#xff0c;成为了一个不可忽视的全球性挑战。认识到网络安…

Python数据分析案例52——基于SSA-LSTM的风速预测(麻雀优化)

案例背景 又要开始更新时间序列水论文的系列的方法了&#xff0c;前面基于各种不同神经网络层&#xff0c;还有注意力机制做了一些缝合模型。 其实论文里面用的多的可能是优化算法和模态分解&#xff0c;这两个我还没出专门的例子&#xff0c;这几天正好出一个优化算法的例子来…

uboot学习:(四)顶层makefile分析

目录 版本号 MAKEFLAGS变量 命令输出 静默输出 设置编译结果输出目录 代码检查&#xff08;一般不需要使用&#xff0c;了解就行&#xff09; 模块编译&#xff08;一般不用uboot编译模块&#xff0c;了解就行&#xff09; 获取主机架构和系统 设置目标架构、交叉编译…

隔离驱动-视频课笔记

目录 1、需要隔离的原因 1.2、四种常用的隔离方案 2、脉冲变压器隔离 2.1、脉冲变压器的工作原理 2.2、泄放电阻对开关电路的影响 2.3、本课小结 3、光耦隔离驱动 3.1、光耦隔离驱动原理 3.2、光耦隔离驱动的电源进行分析 3.3、本课小结 4、自举升压驱动 4.1…

大数据开发中的元数据:从基础到高级应用的全面指南

在大数据开发中&#xff0c;元数据&#xff08;Metadata&#xff09;是指描述数据的数据。元数据可以提供有关数据结构、数据类型、数据约束和数据关系的重要信息。合理利用元数据可以显著提高数据建模和管理的效率。本文将详细介绍如何根据元数据建表&#xff0c;并提供一些代…

2024年公司电脑屏幕监控软件推荐|6款好用的屏幕监控软件盘点!

在当今的商业环境中&#xff0c;确保员工的工作效率和数据安全是每个企业管理者的重要任务。屏幕监控软件通过实时监控和记录员工的电脑活动&#xff0c;帮助企业有效地管理和优化工作流程。 1.固信软件 固信软件https://www.gooxion.com/ 主要特点&#xff1a; 实时屏幕监控…

养殖业饲料加工新选择,粉碎机械提升效率

在当今畜牧业快速发展的时代&#xff0c;饲料加工设备成为提升养殖效益的重要一环。其中&#xff0c;饲料加工粉碎机凭借其G效、便捷的特点&#xff0c;成为了养殖场的得力助手。 饲料加工粉碎机作为养殖业的重要设备之一&#xff0c;其主要功能是将各种原料如玉米、豆粕、麦…

Sentinel限流算法:滑动时间窗算法、漏桶算法、令牌桶算法。拦截器定义资源实现原理

文章目录 滑动时间窗算法基本知识源码算法分析 漏桶算法令牌桶算法拦截器处理web请求 滑动时间窗算法 基本知识 限流算法最简单的实现就是使用一个计数器法。比如对于A接口来说&#xff0c;我要求一分钟之内访问量不能超过100&#xff0c;那么我们就可以这样来实现&#xff1…

(一)高并发压力测试调优篇——MYSQL数据库的调优

前言 在实际项目开发中&#xff0c;很多业务场景下都需要考虑接口的性能要求&#xff0c;追求高并发、高吞吐量。那么对于此类问题如何入手呢&#xff1f;关注作者&#xff0c;不迷路。本节内容主要介绍在数据库db方面的优化&#xff0c;以mysql数据库为例。 关于db的优化&am…

7、matlab实现SGM/BM/SAD立体匹配算法计算视差图

1、matlab实现SGM/BM/SAD立体匹配算法计算视差图简介 SGM&#xff08;Semi-Global Matching&#xff09;、BM&#xff08;Block Matching&#xff09;和SAD&#xff08;Sum of Absolute Differences&#xff09;都是用于计算立体匹配&#xff08;Stereo Matching&#xff09;的…

远程帮客户解决“应用程序无法正常启动0xc000007b,请单击确定关闭应用程序”的问题

今天收到反馈&#xff0c;SmartPipe软件&#xff0c;在客户机器上报错&#xff0c;无法正常运行&#xff0c;采用远程控制软件进入客户电脑&#xff0c;发现电脑报错如下&#xff1a; 因为客户的电脑是win7&#xff0c;而之前发生过win7电脑上无法运行OCC编写的软件的情况&…