UUID v7生成器

Generate time-ordered UUID v7 for database primary keys

格式化

数量:

什么是 UUID v7?

UUID v7 是在 RFC 9562(2024 年 5 月)中标准化的下一代 UUID 格式。它在最高有效位中编码 48 位 Unix 毫秒时间戳,随后是版本和变体标记,其余位填充加密安全随机数据。

由于时间戳占据高位,UUID v7 值按时间顺序排序——无论是字典序还是数字序。这使它们非常适合作为数据库主键,而随机 UUID(v4)会导致 B 树索引碎片化。

为什么随机 UUID 会使数据库索引碎片化

B 树索引——被 PostgreSQL、MySQL、SQLite 和大多数其他数据库使用——按键值排序保存行。当你插入新行时,数据库必须将其放置在索引中正确的排序位置。

使用 UUID v4(完全随机),每次新插入都会落在索引树中本质上随机的位置。这迫使数据库不断读取和重写内部索引页,分裂满页并让其他页半空。结果是一个碎片化、膨胀的索引,随着表的增长,读写速度都会降低。

UUID v7 位布局

UUID v7 宽 128 位,分为六个字段:

位数字段用途
48unix_ts_ms48 位 Unix 毫秒时间戳——占据整个高半部分
4ver版本号——始终为 0111(十进制 7)
12rand_a12 位加密安全随机数据
2var变体标记——始终为 10(RFC 4122 变体)
62rand_b62 位加密安全随机数据

时间戳精度为 1 毫秒。在同一毫秒内,UUID v7 值按随机后缀排序——不保证亚毫秒级单调递增,但它们是 k-sortable(k 可排序)的:时间上相近生成的 ID 在索引中也会相近排列。

UUID v7 vs UUID v1

UUID v1 和 UUID v7 都嵌入了时间戳,但设计上有显著差异:

特性UUID v7UUID v1
纪元 / 时间基准Unix 纪元(1970 年 1 月 1 日)格里高利纪元(1582 年 10 月 15 日)
时间精度1 毫秒100 纳秒
可排序是——按设计 k 可排序否——时间字段在 UUID 布局中被打乱
隐私不泄露主机信息嵌入生成主机的 MAC 地址
数据库索引性能极佳——顺序插入,最小碎片化差——尽管有时间戳但非顺序
标准RFC 9562(2024 年)RFC 4122(2005 年)
原生浏览器支持暂无(无 crypto.randomUUID v7)原生不可用

对于任何需要时间排序 UUID 的新项目,优先选择 UUID v7 而非 UUID v1。UUID v1 是遗留格式且会泄露主机信息。

UUID v7 vs ULID

ULID(通用唯一字典排序标识符)解决与 UUID v7 类似的问题。以下是两者的对比:

UUID v7
  • 遵循 RFC 9562 UUID 标准——与所有 UUID 工具兼容
  • 连字符十六进制格式——通用识别
  • 原生数据库 UUID 列支持
  • 总计 128 位
ULID
  • Crockford Base32 编码——26 个字符,稍微更紧凑
  • 不区分大小写,避免模糊字符(I、L、O、U)
  • 一眼看去更易于人类阅读
  • 需要库——没有原生平台支持

如果你已经在 UUID 生态系统中(PostgreSQL uuid 列、返回 UUID 的 REST API),请使用 UUID v7。如果你从零开始且更喜欢更友好的人类编码,ULID 是合理的替代方案。

在数据库中使用 UUID v7

大多数数据库尚未原生生成 UUID v7,但它可以存储在标准 UUID 列中,并在应用程序代码或通过扩展生成:

PostgreSQL
PostgreSQL:存储在 uuid 列中。pg-uuidv7 扩展添加了 uuid_generate_v7() 服务端函数,如果你需要数据库生成的 ID。
MySQL / MariaDB
MySQL / MariaDB:存储在 BINARY(16)CHAR(36) 列中。在应用程序代码中生成。MySQL 8.0+ 通过 UUID_TO_BIN(UUID(), 1) 支持 v1 的有序 UUID,但 v7 需要应用层生成。
SQLite
SQLite:存储为 TEXT(36 个字符)或 BLOB(16 字节)。在应用程序代码中生成。TEXT 上的字典排序正确,因为 UUID v7 使用固定宽度的时间戳前缀。

代码示例

UUID v7 尚无法通过 crypto.randomUUID() 获得。在原生支持到来之前,使用 uuidv7(npm)等库:

JavaScript (browser / Node.js 20+)
function generateUuidV7() {
  const buf = new Uint8Array(16)
  crypto.getRandomValues(buf)

  const ms = BigInt(Date.now())

  // Embed 48-bit Unix ms timestamp
  buf[0] = Number((ms >> 40n) & 0xFFn)
  buf[1] = Number((ms >> 32n) & 0xFFn)
  buf[2] = Number((ms >> 24n) & 0xFFn)
  buf[3] = Number((ms >> 16n) & 0xFFn)
  buf[4] = Number((ms >> 8n)  & 0xFFn)
  buf[5] = Number(ms & 0xFFn)

  // Set version 7 (0111xxxx)
  buf[6] = (buf[6] & 0x0F) | 0x70

  // Set variant (10xxxxxx)
  buf[8] = (buf[8] & 0x3F) | 0x80

  const hex = [...buf].map(b => b.toString(16).padStart(2, '0')).join('')
  return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`
}

// Node.js 20+ built-in
// import { randomUUID } from 'node:crypto'  // v4 only — no v7 yet in stdlib
Python (uuid7 library)
# pip install uuid7
import uuid_extensions

uid = uuid_extensions.uuid7()
print(uid)                       # e.g. 018e2b3d-1a2b-7000-8000-abc123456789
print(uid.time)                  # Unix ms timestamp embedded in the UUID

# Or as a plain string
from uuid_extensions import uuid7str
print(uuid7str())
PostgreSQL — generate UUID v7
-- PostgreSQL 13+ extension-free implementation
CREATE OR REPLACE FUNCTION uuid_generate_v7()
RETURNS uuid
LANGUAGE sql
AS $$
  SELECT encode(
    set_bit(
      set_bit(
        overlay(
          uuid_send(gen_random_uuid())
          PLACING substring(int8send(floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint) FROM 3)
          FROM 1 FOR 6
        ),
        52, 1
      ),
      53, 1
    ),
    'hex'
  )::uuid;
$$;

-- Usage as a default primary key
CREATE TABLE events (
  id uuid PRIMARY KEY DEFAULT uuid_generate_v7(),
  payload jsonb,
  created_at timestamptz DEFAULT now()
);
TypeScript — extract timestamp from UUID v7
function extractTimestamp(uuid: string): Date {
  const hex = uuid.replace(/-/g, '')
  const ms = parseInt(hex.slice(0, 12), 16)  // first 48 bits = ms timestamp
  return new Date(ms)
}

const uid = '018e2b3d-1a2b-7000-8000-abc123456789'
console.log(extractTimestamp(uid).toISOString())
// → "2024-03-15T10:22:05.259Z"

常见问题

UUID v7 与 UUID v4 向后兼容吗?
是的。UUID v7 使用与所有其他 UUID 版本相同的 128 位、32 个十六进制数字、连字符传输格式。任何存储或传输 UUID 的系统无需更改即可接受 UUID v7。版本半字节(7)和变体位将其标识为检查 UUID 结构的工具的 v7。
UUID v7 是否会暴露生成时间戳?
是的——前 48 位是 Unix 毫秒时间戳,因此任何持有该 UUID 的人都可以确定它大约是何时生成的(精确到最近的毫秒)。如果暴露创建时间对你的用例是个问题,请改用 UUID v4。
我可以使用 UUID v7 作为数据库主键,而不需要单独的 created_at 列吗?
是的。由于 UUID v7 嵌入了毫秒精度的时间戳,你可以解码该值以获得近似的创建时间。但是,为了清晰性和可索引性,许多团队仍然保留一个明确的 created_at 列,仅将 UUID v7 用于 ID 列。
UUID v7 有多少熵?
UUID v7 有 74 位随机数据(rand_a 中的 12 位 + rand_b 中的 62 位)。这略少于 UUID v4 的 122 位,但仍为实际使用提供了一个极其庞大的无碰撞空间。减少的随机性是获得时间戳可排序性的折衷。
UUID v7 是否在浏览器或 Node.js 中原生支持?
截至 2025 年初尚未。RFC 9562 标准于 2024 年 5 月发布,平台支持仍在追赶中。目前请使用 uuidv7 npm 包。未来浏览器和 Node.js 版本可能通过 crypto.randomUUID({ version: 7 }) 或类似 API 提供原生支持。