由 John Doe 三月 26, 2025
你在设计表结构的时候,习惯使用 UUID 值作为主键吗?现在,PostgreSQL 提供了内置的顺序 UUID 生成函数。
特性提交日志
添加 UUID 版本 7 的生成函数。
本次提交引入了一个 SQL 函数uuidv7()
,该函数会按照 RFC 9652 规范生成 UUID 版本 7 的值。UUIDv7 结合了以毫秒为单位的 Unix 时间戳和随机位,兼具唯一性和可排序性。
在该特性的实现中,在毫秒级时间戳之后紧跟着存储了 12 位的亚毫秒部分,这是在 RFC 规范中称为 “rand_a” 的空间。这确保了在一毫秒内具有额外的单调性。rand_a 位还起到了计数器的作用。之所以选择亚毫秒时间戳,是为了在同一后端生成的 UUID 能单调递增,即使系统时钟倒退或在极高频率下生成 UUID 时,也能如此。因此,在同一后端生成的 UUID 的单调性得到了保证。
本次提交还扩展了uuid_extract_timestamp()
函数,以支持 UUID 版本 7。
此外,为现有的 SQL 函数gen_random_uuid()
添加了别名uuidv4()
,以保持一致性。
讨论:https://postgr.es/m/CAAhFRxitJv%3DyoGnXUgeLB_O%2BM7J2BJAmb5jqAT9gZ3bij3uLDA%40mail.gmail.com
示例
很多人使用 UUID 时的一个困扰是,它们不可排序。当然,在某些情况下这可能有其合理性,但一般来说,我们还是希望能在某种程度上判断,给定两个 UUID,哪个生成得更早,哪个更晚,至少能大致判断。
幸运的是,自 2022 年以来,实际上有一种 UUID 格式 / 版本使这成为可能,即 UUID 版本 7。
首先,让我们看看它是什么样的。在 PostgreSQL 18 之前,我们可以使用gen_random_uuid()
函数获取一个 UUID(版本 4),它是完全随机的:
SELECT gen_random_uuid() FROM generate_series(1,10);
gen_random_uuid
--------------------------------------
ba604c90-b70a-4e34-990c-19f6279a7078
a9196baa-2326-4fa2-9e57-5232ae1fee04
ee7076f8-0177-4e36-a7eb-ae7d56fef3e2
1cd2e852-7642-4936-9b45-4a4f821a00ff
feadb02a-2746-4e8e-83a1-31d9c3203049
1a72dc31-c95e-4ce7-b848-ca29a9c614ea
f57ac276-d22b-4d32-8f38-79614a126a7e
f92f98dd-e359-490f-9008-bdf38c86cbdc
39c51f13-9035-43a8-b6aa-c54de897ba08
1c8c0929-65f9-4b9d-abab-63117a028d0b
(10 rows)
在上面的输出中,第三段的值开头都是 4,这是一个版本标记。
现在,在 PostgreSQL 18 中,我们可以生成 UUID v7 的值,如下所示,显然有些与时间相关的部分变化不大:
SELECT uuidv7() FROM generate_series(1,10);
uuidv7
--------------------------------------
01941c04-4185-7ea3-ab00-82c8a75adf41
01941c04-4185-7efe-b171-12949bdf8bd8
01941c04-4185-7f05-89b4-0409545aefc2
01941c04-4185-7f09-9952-b4b515d8b0c8
01941c04-4185-7f0d-a009-252decd3c9ea
01941c04-4185-7f10-89d1-651c6b36fdbb
01941c04-4185-7f14-a77e-edccd6f145bf
01941c04-4185-7f18-b109-c02d263b7457
01941c04-4185-7f1b-8185-24c7c304b7e2
01941c04-4185-7f1f-a950-dec8a092fbe5
(10 rows)
有了这些值,你现在还可以使用uuid_extract_timestamp()
函数,来获取 UUID 的创建时间戳:
SELECT uuid_extract_timestamp('01941c04-4185-7efe-b171-12949bdf8bd8'::uuid);
uuid_extract_timestamp
----------------------------
2024-12-31 17:20:28.549+08
(1 row)
根据规范,时间戳的精度为毫秒。这对于绝大多数场景来说应该足够了。
还有一个有趣的功能,它允许生成带有一定时间偏移的 UUID。
例如,如果我对新生成的 UUID v7 运行uuid_extract_timestamp
,得到的结果会大致与now()
相同:
SELECT uuid_extract_timestamp(uuidv7()), now();
uuid_extract_timestamp | now
----------------------------+-------------------------------
2024-12-31 17:27:41.122+08 | 2024-12-31 17:27:41.122049+08
(1 row)
但是,我也可以这样做:
SELECT uuid_extract_timestamp(uuidv7('-21 years'));
uuid_extract_timestamp
----------------------------
2003-12-31 17:28:11.544+08
(1 row)
看起来确实很有用。感谢社区的所有相关人员。
参考
提交日志:https://git.postgresql.org/pg/commitdiff/78c5e141e9c139fc2ff36a220334e4aa25e1b0eb