最稳妥方式是使用 github.com/google/uuid 库的 uuid.new() 或 uuid.newstring(),它符合 rfc 4122、线程安全、自动处理版本与变体位;避免手写 crypto/rand 位操作或使用已归档的老库。

Go 用 google/uuid 生成 v4 UUID 最稳妥
别碰手写位操作或老库(比如已归档的 satori/go.uuid),github.com/google/uuid 是当前 Go 社区事实标准,兼容 RFC 4122、线程安全、性能好。v4 足够绝大多数业务场景——它靠加密随机数生成,冲突概率可忽略。
安装:go get github.com/google/uuid
生成示例:
id := uuid.NewString() // 返回形如 "a1b2c3d4-e5f6-4g7h-8i9j-0k1l2m3n4o5p" 的字符串 // 或者用 uuid.UUID 类型(更类型安全) uid := uuid.New() idStr := uid.String()
- 不用自己调
crypto/rand+ 位运算,uuid.New()内部已封装完整逻辑 -
uuid.NewString()比uuid.New().String()少一次内存分配,压测下有微小优势 - 避免用
uuid.Must(uuid.NewV4())这类旧写法——google/uuid没有NewV4方法,直接用New
Gin 请求参数校验时怎么验 UUID 字符串
用 validator 的 uuid tag 即可,但注意:它只校验格式,不保证存在性或业务有效性。
type CreateOrderReq struct {
ID string `json:"id" validate:"uuid"`
Name string `json:"name" validate:"required,min=1,max=50"`
}
- 必须是标准 UUID 格式(32 字符 + 4 个连字符),
550e8400e29b41d4a716446655440000(无连字符)会失败 - 如果前端传的是去掉连字符的紧凑格式,得自定义 validator,比如注册一个
uuid_no_dash规则 - 不要在
validate里查数据库判断 ID 是否已存在——那是业务逻辑层该干的事,校验器只管“是不是合法 UUID”
PostgreSQL 插入后怎么拿到生成的 UUID 主键
别用 db.Exec().LastInsertId()——它对 UUID 完全无效,会返回 0 或 panic。
必须用 RETURNING + QueryRow().Scan():
query := `INSERT INTO orders (id, name, amount) VALUES ($1, $2, $3) RETURNING id` var insertedID string err := db.QueryRow(query, uuid.NewString(), req.Name, req.Amount).Scan(&insertedID)
- 确保 PostgreSQL 表字段是
UUID类型(不是TEXT),并配DEFAULT gen_random_uuid()(需启用pgcrypto扩展) - 如果驱动用的是
pgx,可直接 Scan 到pgtype.UUID类型;用lib/pq就用string,更简单 - 事务中执行时,
RETURNING天然属于当前事务上下文,无需额外处理
UUID 当主键时容易被忽略的三个坑
UUID 不是“设了就完事”的银弹,尤其在 Gin + GORM 或原生 SQL 场景下:
- 数据库索引性能:v4 UUID 无序,高频插入会导致 B+ 树页分裂加剧,比自增 ID 慢 3–5 倍;若业务有强时间排序需求(如订单列表),优先考虑
v7(需用github.com/oklog/ulid或手动实现) - GORM 模型定义:用
type ID string接 UUID 字段时,GORM 默认不会自动设主键,必须显式加primaryKeytag,且type要匹配(type:uuid或type:varchar(36)) - API 返回一致性:前端可能期望 ID 是字符串,但某些 ORM(如 GORM)默认把 UUID 字段序列化成字节数组或对象;务必检查 JSON 输出是否为标准字符串格式,必要时重写
MarshalJSON











