本月早些时候,Postgres 提交了 UUIDv7 的实现。UUIDv7 具有 v4(随机)UUID 的所有优点,但通过使用当前时间生成,具有更确定的顺序,并且在插入使用有序结构(如 B 树)时性能显著提升。
一个令人惊喜的特性是,UUID 的随机部分在每个 Postgres 后端中将是单调递增的:
在我们的实现中,12 位的亚毫秒时间戳部分紧跟在时间戳之后存储,位于 RFC 中称为“rand_a”的空间中。这确保了在毫秒内的额外单调性。rand_a 位还充当计数器。我们选择亚毫秒时间戳,以便在同一后端内生成的 UUID 单调递增,即使系统时钟回退或在极高频率下生成 UUID。因此,生成的 UUID 在同一后端内的单调性得到了保证。
这一特性在实践中极具价值,尤其是在测试中。假设您想生成五个对象以测试 API 列表端点。它们可能由于跨越不同的毫秒或运气好而按顺序生成,但概率对您不利,很可能某些对象会乱序。测试用例必须生成五个对象,然后在使用它们之前进行初始排序。这并非世界末日,但会增加测试代码的复杂性并引入噪音。
test_accounts = 5.times { TestFactory.account }
# 可能 ID 是按顺序的,但也可能不是,因此进行初始排序
test_accounts.sort_by! { |a| a.id }
# 堆代码 duidaima.com
# API 端点将按 ID 顺序返回账户
resp = make_api_request :get, "/accounts"
expect(resp.map { _1["id"] }).to eq(test_accounts.map(&:id))
Postgres 补丁通过重新利用 UUID 随机部分的 12 位来提高时间戳的精度,达到纳秒级(填充上图中的 rand_a),这在实际中过于精确,以至于在同一进程中无法生成两个 UUIDv7。它增加了进程之间重复 UUID 的可能性,但仍有 62 位随机性可用,因此碰撞的可能性仍然极低。
等待中
UUIDv7 将成为 Postgres 的核心功能之一,我迫不及待地想开始使用它们。遗憾的是,它们的提交被推迟到 Postgres 17 的冻结期之后,因此它们要到 2025 年底 Postgres 18 发布时才会正式推出。所以现在,我们只能等待。