Skip to content
Go
uuid — RFC 9562 通用唯一标识符 (Go 1.27+)

uuid — RFC 9562 通用唯一标识符 (Go 1.27+)

Go 1.27 把长期由第三方库(google/uuid 等)占据的领域收编进标准库:顶层 uuid 包。它只做两件事——生成 UUID(V4 随机、V7 时间有序)和解析/比较/文本互转。随机源直接用了密码学安全的随机数,不必再担心"用了 math/rand 生成 ID"这种经典事故。

import "uuid" // 注意:顶层路径,不带域前缀

1. API 一览(很小,够用)

u := uuid.New()          // 当前等价于 NewV4
u := uuid.NewV4()        // 122 位随机 + 版本/变体位
u := uuid.NewV7()        // 48 位毫秒时间戳 + ≥62 位随机

u2, err := uuid.Parse("550e8400-e29b-41d4-a716-446655440000")
u3 := uuid.MustParse(s)  // 解析失败直接 panic——只用于字面量/测试

u.String()               // 标准 8-4-4-4-12 文本
cmp := u.Compare(v)      // -1/0/1,按 RFC 字节序
u.Variant(); u.Version() // 不存在——本包未提供(go doc 实测)
b, _ := u.AppendText(nil)
text, _ := u.MarshalText()   // JSON/表单编码自动走它
err := (&u).UnmarshalText(text)

uuid.Nil()               // 全零 "00000000-...-000000000000"
uuid.Max()               // 全 "f",排序上界哨兵值

UUID 是 [16]byte 数组类型,可直接用 == 比较、可做 map key——不需要像第三方库那样先转字符串再比。

2. 与 JSON 的集成

因为实现了 MarshalText/UnmarshalText,encoding/json 直接把它当字符串编解码:

type Order struct {
    ID     uuid.UUID `json:"id"`
    Items  []Item    `json:"items"`
}

o := Order{ID: uuid.NewV7()}
b, _ := json.Marshal(o) // {"id":"0198...-...","items":[...]}

var got Order
json.Unmarshal(b, &got) // got.ID 正常还原

易错:uuid.UUID 是数组,零值是 00000000-... 而不是 nil。判断"没填"要用 id == uuid.Nil(),不是 id == nil(编译都过不了);omitempty 对固定大小数组不生效,不想输出全零 ID 就别依赖 omitempty。

3. V4 还是 V7——这是真实的选型题

V4(纯随机) V7(时间有序)
结构 全随机 前 48 位 = 毫秒 Unix 时间戳
排序性 完全随机 大致按创建时间递增
信息泄露 无 暴露创建时间(精度到毫秒)
数据库主键 B+ 树随机插入,页分裂/随机 IO 多 追加式插入,索引局部性好

经验选择:

  • 主键/订单号/Feed ID → NewV7()。InnoDB、PostgreSQL 等 B+ 树主键最怕随机分布的 ID——UUIDv4 作为主键会把索引搅成一团散弹,插入性能显著劣化、缓存命中率差。V7 的时间前缀让"新行总是落在树最右侧",接近自增主键的表现。
  • 对外暴露、且不能泄露任何时间信息(URL 里的资源 token、幂等键)→ NewV4()。V7 的毫秒时间戳会告诉观察者创建时间,有时正是你不想要的。
  • 拿不准就用 New()——官方文档明说"没有特定算法需求就用 New"(当前 = V4,将来可能变)。

注意:V7 保证"除系统时钟回拨外排序递增"。NTP 校时回拨时会出现乱序——对"按 ID 排序近似按时间排序"的设计不要做成强一致依赖。

4. 实际场景配方

// 1) API 幂等键:客户端生成、服务端去重
key := uuid.NewV4().String()
req.Header.Set("Idempotency-Key", key)
// 服务端:SETNX idem:<key>,配合 TTL 去重

// 2) 分布式追踪:128 位 ID 天然适配 W3C traceparent
traceID := uuid.New() // 直接写进 32 位 hex 字段

// 3) JWT jti / 会话令牌:必须用本包(内部走 crypto/rand),
//    别拿 math/rand 拼字符串冒充随机 ID

何时不必用 uuid:单库自增/雪花已够用且没有跨库合并需求时;短展示码(用户手输的对账单号)用 8 位 Base32 更省地方;只需要"唯一"不需要"全局可排序"时,rand.Text/crypto token 也可以。16 字节不是零成本——ID 越长,索引越大、URL 越丑。

5. 能力边界

标准库 uuid 只实现了 V4 与 V7(RFC 9562 定义的常用两型)。需要 V1(MAC+时间)、V3/V5(命名空间哈希)、V6(重排时间) 的,本包没有 Version 之外的构造函数,只能继续用 google/uuid 等第三方。迁移存量数据时尤其注意:库里存的若是 V1/V5,标准库能 Parse 并比较,但不能生成。


6. 实战建议

  • 生成一律 NewV4/NewV7,别用 New() 写死在对外协议文档里(语义可能随版本漂移)。
  • MustParse 只给常量字面量和测试用;处理外部输入用 Parse + 错误分支。
  • 存数据库选 BINARY(16) 或 UUID 列,别存 CHAR(36) 字符串——占空间、比较慢、还引入大小写坑。
  • 升 Go 1.27 时把项目里手搓 rand.Read + 格式化 的 ID 生成逐步收敛到本包;go vet 的 stdversion 检查会提示哪些文件用了太新特性,反过来迁移期可用 //go:build go1.27 隔离新旧实现。