UUID(通用唯一标识符)是 128 位标识符,用于在分布式系统中唯一标识信息。它们无处不在——在数据库、API、文件名和交易 ID 中。本文涵盖 UUID 版本、随机性属性、碰撞概率,以及如何为您的应用程序选择正确的版本。
什么是 UUID?
UUID 是一个 128 位数字,通常显示为 5 组由连字符分隔的 32 个十六进制字符:
550e8400-e29b-41d4-a716-446655440000
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
↑ ↑
M = 版本(4)
N = 变体(10xx)
可能的 UUID 总数为 2^128 ≈ 3.4 × 10^38——这个数字如此之大,以至于碰撞实际上是不可能的。
UUID 版本
版本 1(时间戳 + MAC 地址)
d9b7e3a0-1f8c-11ee-95c4-0242ac120002
↑
版本 1
结构:
- 60 位时间戳(自 1582 年 10 月 15 日以来的 100 纳秒间隔)
- 48 位生成计算机的 MAC 地址
- 14 位时钟序列(处理时间戳碰撞)
属性:
- 按时间排序:可按生成时间排序
- 唯一:MAC 地址确保跨机器唯一性
- 隐私问题:泄露 MAC 地址和生成时间
使用场景:
- 需要按时间排序的 UUID
- 需要在分布式系统中唯一
- 隐私不是问题
版本 4(随机)
a1b2c3d4-e5f6-4789-abcd-ef0123456789
↑
版本 4
结构:
- 122 位随机数据
- 4 位版本(0100)
- 2 位变体(10)
属性:
- 随机:无嵌入信息
- 无需协调:独立生成
- 隐私安全:无信息泄露
- 不可排序:随机顺序
使用场景:
- 需要简单的随机标识符
- 隐私很重要
- 不需要时间排序
- 最常用的通用选择
版本 5(基于 SHA-1 的名称)
6ba7b810-9dad-11d1-80b4-00c04fd430c8
↑
版本 5
结构:
- 以命名空间 UUID 和名称作为输入
- 计算组合的 SHA-1 哈希
- 产生确定性输出(相同输入 = 相同 UUID)
属性:
- 确定性:相同的命名空间 + 名称总是产生相同的 UUID
- 无随机性:可重现
- 抗碰撞:SHA-1 提供良好的分布
使用场景:
- 需要从已知输入生成确定性 UUID
- 多个系统必须为同一实体生成相同的 UUID
- 示例:从 URL 生成 UUID、从域名生成 UUID
版本 3(基于 MD5 的名称)
与版本 5 相同的概念,但使用 MD5 代替 SHA-1:
- 比版本 5 抗碰撞能力弱
- 新系统应使用版本 5
UUID 格式详情
结构
八位字节: 0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | time_low |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4 | time_mid | time_hi_and_version |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
8 |clk_seq_hi_res | clk_seq_low | node (0-1) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12 | node (2-5) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
版本和变体位
| 版本 | 位(M 位置) | 变体 | 位(N 位置) |
|---|---|---|---|
| 1 | 0001 | 1 | 10xx |
| 3 | 0011 | 1 | 10xx |
| 4 | 0100 | 1 | 10xx |
| 5 | 0101 | 1 | 10xx |
碰撞概率
对于版本 4(随机)UUID:
| UUID 数量 | 碰撞概率 |
|---|---|
| 10 亿 | 10^18 分之一 |
| 1 万亿 | 10^12 分之一 |
| 10^18 | ~50% 几率 |
要有 50% 的几率至少发生一次碰撞,您需要生成大约 2.71 × 10^18 个 UUID。那是 271 亿亿个 UUID。
出于实际目的,版本 4 UUID 对于任何现实世界的应用都是无碰撞的。
生日问题
生日问题解释了为什么碰撞比直觉认为的更早变得可能。对于 n 个可能的值,您需要大约 √n 个项目才能有 50% 的碰撞几率。
对于 UUID:√(2^128) ≈ 1.8 × 10^19
版本 1 vs 版本 4
| 方面 | 版本 1 | 版本 4 |
|---|---|---|
| 排序 | 按时间排序 | 随机 |
| 唯一性来源 | MAC + 时间戳 | 随机性 |
| 隐私 | 泄露 MAC/时间 | 无信息泄露 |
| 数据库性能 | 良好(顺序插入) | 还行(随机插入) |
| 需要协调 | 无 | 无 |
| 常见用途 | 遗留系统 | 现代应用 |
数据库考虑
版本 1 优势:
- 按时间排序:对聚集索引更好
- 顺序插入减少 B-tree 索引中的页面分裂
版本 4 劣势:
- 随机:在聚集索引中导致索引碎片
- 可以通过 UUID v7(时间排序随机)或 ULID 缓解
版本 7(草案):
- 时间排序的随机 UUID
- 两全其美
- 尚未标准化
UUID 作为数据库主键
优势
- 全局唯一:对分布式数据库安全
- 无需协调:不需要自增序列
- 合并安全:可以合并数据库而不冲突
- 不透明:不泄露记录数量
劣势
- 大小:16 字节 vs 4 字节(int)或 8 字节(bigint)
- 索引碎片:随机 UUID 导致页面分裂
- 不友好:难以调试
最佳实践
-- 对聚集索引使用版本 1 或 ULID CREATE TABLE orders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 版本 4 -- 或 id UUID PRIMARY KEY DEFAULT uuid_generate_v1(), -- 版本 1 ... ); -- 存储为 UUID 类型(不是 varchar) -- PostgreSQL 有原生 UUID 类型 -- MySQL 存储为 BINARY(16) 或 CHAR(36)
API 中的 UUID
URL 参数
GET /users/550e8400-e29b-41d4-a716-446655440000
JSON 响应
{ "id": "550e8400-e29b-41d4-a716-446655440000", "name": "John Doe" }
请求 ID
UUID 通常用作分布式系统中的请求/关联 ID,用于跨服务跟踪请求。
UUID 替代方案
| 格式 | 大小 | 按时间排序 | 示例 |
|---|---|---|---|
| UUID v4 | 16 字节 | 否 | 550e8400-e29b-41d4-... |
| UUID v1 | 16 字节 | 是 | d9b7e3a0-1f8c-11ee-... |
| ULID | 16 字节 | 是 | 01ARZ3NDEKTSV4RRFFQ69G5FAV |
| NanoID | 可变 | 否 | V1StGXR8_Z5jdHi6B-myT |
| Snowflake | 8 字节 | 是 | 492474761028608 |
| CUID | 25 字符 | 是 | cjld2cyuq0000t3rmniod1foy |
ULID(通用唯一字典排序标识符)
- 128 位,与 UUID 相同
- 按时间排序(可字典排序)
- 编码为 26 字符的 Crockford Base32
- 比随机 UUID 更适合数据库
NanoID
- 可变长度(默认 21 字符)
- URL 安全
- 比 UUID 小
- 适合短标识符
生成 UUID
JavaScript
// 版本 4(随机) const uuid = crypto.randomUUID(); // "550e8400-e29b-41d4-a716-446655440000" // 指定版本 import { v4 as uuidv4 } from 'uuid'; const id = uuidv4();
Python
import uuid # 版本 4(随机) id_v4 = uuid.uuid4() # 版本 1(时间戳 + MAC) id_v1 = uuid.uuid1() # 版本 5(基于名称) id_v5 = uuid.uuid5(uuid.NAMESPACE_URL, 'https://example.com')
命令行
# 使用 uuidgen(macOS/Linux) uuidgen # 使用 Python python -c "import uuid; print(uuid.uuid4())" # 使用 OpenSSL openssl rand -hex 16 | sed 's/\(.\{8\}\)\(.\{4\}\)\(.\{4\}\)\(.\{4\}\)\(.\{12\}\)/\1-\2-\3-\4-\5/'
常见错误
| 错误 | 问题 | 解决方案 |
|---|---|---|
| 使用 UUID v4 作为主键 | 索引碎片 | 使用 v1、v7 或 ULID |
| 存储为 VARCHAR(36) | 浪费空间 | 使用原生 UUID 类型或 BINARY(16) |
| 假设可排序 | UUID v4 是随机的 | 使用 v1 或 ULID 进行排序 |
| 不索引 UUID 列 | 查询缓慢 | 始终索引 UUID 列 |
| 使用版本 3(MD5) | 比版本 5 弱 | 对基于名称的 UUID 使用版本 5 |
支付系统中的 UUID
UUID 在支付系统中用于:
- 交易 ID:每笔交易的唯一标识符
- 关联 ID:跨服务跟踪请求
- 终端 ID:标识 POS 终端
- 批次 ID:分组相关交易
在线工具
使用 UUID 生成器:
- 生成不同版本的 UUID(v1、v4、v5)
- 理解每个版本的结构和组件
- 批量生成 UUID 用于测试
- 验证现有 UUID
所有处理都在浏览器中进行——您的数据永远不会离开您的设备。