# UUID 生成：v1、v4、v5 及使用场景

> UUID 全面指南——版本差异、随机性保证、碰撞概率，以及为数据库和 API 选择正确的 UUID 版本。

Canonical: https://hsmkit.com/zh/guides/uuid-generation-guide/

Last updated: 2026-05-21

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 导致页面分裂
- **不友好**：难以调试

### 最佳实践

```sql
-- 对聚集索引使用版本 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 响应

```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

```javascript
// 版本 4（随机）
const uuid = crypto.randomUUID();
// "550e8400-e29b-41d4-a716-446655440000"

// 指定版本
import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
```

### Python

```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')
```

### 命令行

```bash
# 使用 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)：

- 生成不同版本的 UUID（v1、v4、v5）
- 理解每个版本的结构和组件
- 批量生成 UUID 用于测试
- 验证现有 UUID

所有处理都在浏览器中进行——您的数据永远不会离开您的设备。
