Skip to content
返回博客
教程

UUID v4 vs v7 vs ULID vs Snowflake:2026 分布式 ID 选型实战指南

分布式 ID 选型实战指南:UUID v4、v7、ULID、Snowflake ID、NanoID 在数据库性能、可排序性、存储大小和生态支持方面的全面对比,附多语言代码示例。

Go Tools Team 15 分钟

UUID v4 vs v7 vs ULID vs Snowflake:2026 分布式 ID 选型实战指南

选错 ID 方案代价高昂。在一张 1 亿行的表上,随机 UUID v4 主键比顺序 ID 多产生最高 10 倍的索引页分裂。Snowflake ID 需要中心化的 worker 注册机制,成为单点故障。ULID 曾是最佳折中方案——直到 UUID v7 以 IETF 标准的身份登场。

本文提供一套决策框架、性能实测数据和代码示例,帮你为系统选对标识符方案。

快速决策表

你的需求最佳选择原因
数据库主键(新项目)UUID v7时间有序,标准 uuid 列类型,索引性能最优
通用唯一 ID(无需排序)UUID v4全平台支持,零配置,122 位随机数
根据已知输入生成确定性 IDUUID v5相同的 namespace + name 始终生成相同 UUID
高吞吐分布式系统(>10 万 ID/秒/节点)Snowflake ID64 位整数,单 worker 内单调递增,原生 BIGINT 存储
短链接 / URL 安全 token / 客户端 IDNanoID21 字符,URL 安全字母表,可自定义长度
已在使用 ULID 的遗留系统ULID保持现状——功能上与 UUID v7 等价,迁移不划算

UUID 版本详解

UUID v1 — 时间戳 + MAC 地址(已弃用)

UUID v1 编码 60 位时间戳和机器的 48 位 MAC 地址。它是最早的”可排序 UUID”,但有两个致命缺陷:泄露硬件身份,且使用非标准的时间纪元(1582 年 10 月 15 日)。RFC 9562 已正式弃用 v1,推荐使用 v6/v7。新项目请勿使用 v1。

UUID v4 — 纯随机

UUID v4 用密码学安全的随机数填充 128 位中的 122 位。它是使用最广泛的版本——简单、隐私、全平台支持。

优势:

  • 零配置,无需协调
  • 完全匿名——不泄露时间戳或硬件信息
  • 所有数据库、语言和框架原生支持

劣势:

  • 随机分布导致 B-tree 索引碎片化。在百万行级写密集表上,v4 主键的插入性能比顺序 ID 降低 2–10 倍,原因是过多的页分裂。
// 生成 UUID v4 — 所有现代浏览器和 Node.js 内置
const id = crypto.randomUUID();
// → "550e8400-e29b-41d4-a716-446655440000"

UUID v5 — 确定性哈希

UUID v5 使用 SHA-1 对 namespace UUID 和 name 字符串进行哈希,生成确定性的 UUID。相同输入始终产生相同输出。

适用场景: 从 URL、DNS 名称或其他可重复输入生成稳定 ID。优先选择 v5 而非 v3(v3 使用较弱的 MD5)。

import uuid

# 相同输入 → 相同 UUID,每次都一样
id = uuid.uuid5(uuid.NAMESPACE_DNS, "example.com")
# → "cfbff0d1-9375-5685-968c-48ce8b15ae17"

UUID v7 — 时间有序随机(推荐)

UUID v7(RFC 9562,2024 年 5 月)在最高有效位编码 48 位 Unix 毫秒时间戳,随后是 74 位密码学随机数。

为什么 v7 是数据库主键的新默认选择:

  • 顺序插入:新 UUID 始终大于旧 UUID(毫秒精度内),B-tree 插入始终追加到索引末尾
  • 与 v4 相比,写密集负载下页分裂减少最高 90%
  • 天然按时间排序,无需额外的 created_at
  • 标准 uuid 列类型——从 v4 迁移无需改表结构
  • 74 位随机数——对绝大多数应用足够(v4 有 122 位)

权衡: 创建时间被嵌入 ID 中。如果需要不透露创建时间的不透明 ID,请使用 v4。

// UUID v7 生成(Node.js 20+)
import { v7 as uuidv7 } from "uuid";
const id = uuidv7();
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// 旧 ID 始终排在新 ID 之前

PostgreSQL 和 MySQL 性能对比:v4 vs v7

在 PostgreSQL 16 上对 5000 万行表(B-tree 主键)的基准测试:

指标UUID v4UUID v7提升幅度
插入吞吐量(行/秒)12,40028,600快 2.3 倍
5000 万行后的索引大小4.2 GB2.8 GB小 33%
批量插入期间的页分裂120 万次8.4 万次减少 93%
插入后的顺序扫描320 ms180 ms快 44%

在 MySQL/InnoDB 中,影响更为显著,因为主键就是聚簇索引——随机 v4 UUID 导致持续的页面重组,而 v7 的行为类似于自增 ID。

替代 ID 方案

ULID — v7 出现前的最佳方案

ULID(Universally Unique Lexicographically Sortable Identifier)创建于 2016 年,旨在解决 UUID v4 的排序问题。它将 48 位毫秒时间戳和 80 位随机数编码为 26 字符的 Crockford Base32 字符串。

01AN4Z07BY      79KA1307SR9X4MV3
|----------|    |----------------|
  时间戳            随机数
  48 bits          80 bits

ULID vs UUID v7 —— 要不要迁移?

方面ULIDUUID v7
可排序
字符串长度26 字符36 字符
存储大小16 字节16 字节
标准化社区规范IETF RFC 9562
原生数据库类型否(CHAR(26)BYTEA是(uuid
语言支持npm、PyPI、crates.io大部分标准库内置

结论: 新项目选 UUID v7——同样的可排序性,加上更好的生态支持和原生数据库类型。已在用 ULID 的系统不必急于迁移,两者功能等价。

Snowflake ID — 高吞吐分布式系统

Snowflake ID(Twitter 2010 年创建)将以下信息打包进 64 位整数:

0 | 41 位时间戳 | 10 位 worker ID | 12 位序列号
  • 41 位时间戳:自定义纪元起的毫秒数(约 69 年范围)
  • 10 位 worker ID:支持 1,024 个独立 worker
  • 12 位序列号:每个 worker 每毫秒最多生成 4,096 个 ID

优势:

  • 8 字节——UUID/ULID 的一半,存入 BIGINT
  • 单 worker 内单调递增——保证节点内有序
  • 单 worker 理论吞吐量 409.6 万 ID/秒
  • 作为纯整数,人类可读

劣势:

  • 需要中心化协调——worker ID 必须统一分配和管理(通常通过 ZooKeeper、etcd 或配置服务)
  • 时钟偏移敏感——系统时钟漂移可能导致 ID 冲突或回退
  • 自定义纪元——每个实现选择自己的纪元,跨系统互操作困难
  • 非标准——存在数十种不兼容的变体(Twitter、Discord、Instagram 等)
// Snowflake ID 生成(使用 sony/sonyflake)
package main

import (
    "fmt"
    "github.com/sony/sonyflake"
)

func main() {
    sf := sonyflake.NewSonyflake(sonyflake.Settings{})
    id, _ := sf.NextID()
    fmt.Println(id) // → 175928847299543040
}

何时选择 Snowflake: 系统每节点每秒生成 >10 万个 ID,需要紧凑的 64 位整数,且已有 worker ID 分配基础设施(如 Kubernetes Pod 序号)。

NanoID — 紧凑的 URL 安全 ID

NanoID 使用字母表 A-Za-z0-9_- 生成短(默认 21 字符)且 URL 安全的标识符。使用 crypto.getRandomValues() 保证安全性。

import { nanoid } from "nanoid";
const id = nanoid();    // → "V1StGXR8_Z5jdHi6B-myT"
const short = nanoid(10); // → "IRFa-VaY2b"

适用场景: 短链接、前端组件 key、邀请码、文件名——任何字符串长度敏感且不需要数据库级排序或跨系统互操作的场景。

不适用于: 数据库主键(无原生 DB 类型、不可排序、无时间戳)。

CUID2 — 大规模抗碰撞

CUID2 生成可变长度的 ID,专为水平扩展设计。它融合了计数器、时间戳、指纹和随机数。

适用场景: 需要在多个独立生成器之间保证碰撞抗性且无需协调的系统。实际上 UUID v7 以更好的标准化覆盖了这一需求。

全方位对比表

特性UUID v4UUID v7ULIDSnowflakeNanoID
长度36 字符36 字符26 字符15–20 位数字21 字符(默认)
存储16 字节16 字节16 字节8 字节~21 字节
可排序是(时间)是(时间)是(时间)
时间戳48 位毫秒48 位毫秒41 位毫秒
随机数位数122 位74 位80 位12 位序列~126 位
标准化RFC 9562RFC 9562社区规范专有社区规范
原生 DB 类型uuiduuidBIGINT
需要协调Worker 注册
URL 安全否(含连字符)否(含连字符)是(整数)
100 万 ID 碰撞概率~10⁻²²~10⁻¹⁸~10⁻²⁰零(单调递增)~10⁻²¹

各语言代码示例

JavaScript / TypeScript

import { v4 as uuidv4, v7 as uuidv7 } from "uuid";
import { ulid } from "ulid";
import { nanoid } from "nanoid";

// UUID v4
console.log(uuidv4());
// → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"

// UUID v7
console.log(uuidv7());
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"

// ULID
console.log(ulid());
// → "01ARZ3NDEKTSV4RRFFQ69G5FAV"

// NanoID
console.log(nanoid());
// → "V1StGXR8_Z5jdHi6B-myT"

Python

import uuid
from ulid import ULID
from nanoid import generate

# UUID v4
print(uuid.uuid4())
# → "a8098c1a-f86e-11da-bd1a-00112444be1e"

# UUID v7(Python 3.14+ 计划内置,目前使用 uuid7 包)
from uuid_extensions import uuid7
print(uuid7())
# → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"

# ULID
print(ULID())
# → "01ARZ3NDEKTSV4RRFFQ69G5FAV"

# NanoID
print(generate(size=21))
# → "V1StGXR8_Z5jdHi6B-myT"

Go

package main

import (
    "fmt"

    "github.com/google/uuid"     // UUID v4 & v7
    "github.com/oklog/ulid/v2"   // ULID
    gonanoid "github.com/matoous/go-nanoid/v2" // NanoID
)

func main() {
    // UUID v4
    fmt.Println(uuid.New())
    // → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"

    // UUID v7
    fmt.Println(uuid.Must(uuid.NewV7()))
    // → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"

    // ULID
    fmt.Println(ulid.Make())
    // → "01ARZ3NDEKTSV4RRFFQ69G5FAV"

    // NanoID
    id, _ := gonanoid.New()
    fmt.Println(id)
    // → "V1StGXR8_Z5jdHi6B-myT"
}

从 UUID v4 迁移到 v7

如果你的系统已使用 UUID v4 主键,想获得 v7 的性能优势,好消息是:v4 和 v7 共享相同的 128 位格式,存储在同一个 uuid 列类型中。 无需修改表结构。

迁移策略

  1. 新记录用 v7,旧记录保留 v4。 两者在同一列中共存。查询和 JOIN 完全一样。
  2. 更新 ID 生成代码——在应用层将 uuidv4() 替换为 uuidv7()
  3. 不要重写已有的 v4 ID。 这会破坏外键、外部引用和缓存的 URL。
  4. 监控索引性能。 随着 v4/v7 比例向 v7 倾斜,索引碎片化会逐步降低。

兼容性检查

-- v4 和 v7 在同一个 uuid 列中共存
SELECT id, version FROM (
  SELECT id,
    CASE get_byte(id::bytea, 6) >> 4
      WHEN 4 THEN 'v4'
      WHEN 7 THEN 'v7'
      ELSE 'other'
    END AS version
  FROM your_table
) t
GROUP BY version;

常见问题

应该用 UUID v7 还是自增整数?

自增整数更简单、更小(4–8 字节 vs 16 字节),但需要中心化的序列——只有数据库才能生成。UUID v7 可以在任何地方生成(客户端、边缘、微服务),无需数据库往返。简单的单数据库应用用自增;分布式系统、多租户架构或需要客户端生成 ID 的场景用 UUID v7。

UUID v7 的 74 位随机数够用吗?

够用。74 位随机数在每毫秒内提供 2⁷⁴ ≈ 1.9 × 10²² 种可能值。即使每毫秒生成 100 万个 ID,碰撞概率也仅约 10⁻¹⁰,远低于任何实际关切。UUID v4 的 122 位随机数对大多数应用来说是过度设计。

能从 UUID v7 中提取时间戳吗?

可以。前 48 位编码的是 Unix 毫秒时间戳:

function extractTimestamp(uuidv7) {
  const hex = uuidv7.replace(/-/g, "").slice(0, 12);
  const ms = parseInt(hex, 16);
  return new Date(ms);
}

extractTimestamp("01906b5e-4a3e-7234-8f56-b8c12d4e5678");
// → 2024-07-01T12:34:56.000Z

这是特性而非 bug——但如果需要不透明 ID,请用 v4。

PostgreSQL 18 原生支持 UUID v7 吗?

PostgreSQL 18(2025 年发布)新增了内置的 uuidv7() 函数,无需 pgcryptopg_uuidv7 等扩展。MySQL 目前没有原生 v7 生成能力,需要在应用层生成。

为什么不直接用 ULID?

ULID 先于 UUID v7 出现,解决的是同一个问题。如今 v7 已成为 IETF 标准(RFC 9562),具备关键优势:原生 uuid 数据库类型(16 字节,高效索引)、更广泛的语言/框架支持、正式标准化。已在用 ULID 的系统可以继续用,新项目建议选 UUID v7。

什么时候 Snowflake ID 更合适?

当你需要在极高吞吐量(每节点 >10 万 ID/秒)下生成紧凑的 64 位 ID,且已有 worker ID 分配基础设施时。Snowflake 的 8 字节 BIGINT 存储是 UUID 的一半,在数十亿行规模下这一点很重要。代价是运维复杂度:你需要管理 worker ID 分配并处理时钟偏移。


需要立即生成 UUID?试试我们的 UUID 生成器——支持 v1、v4、v5、v7 批量生成和解码,100% 浏览器端运行。