← 返回文章列表

UUID 与随机标识符:能当主键吗

标识符入门

常见版本

  • v1:基于时间戳 + MAC,可排序但可能泄露机器信息;
  • v4:纯随机,最常用,碰撞概率极低;
  • v7:带时间前缀、可按插入排序,新项目推荐。

作主键的取舍

优点缺点
分布式可独立生成比自增整数长,索引更大
不暴露总量 / 顺序无序写入让 B+ 树索引碎裂

两个提醒

  • 不是密码学秘密:UUID 可枚举,别用它做重置令牌或权限依据;
  • 需要排序用 v7:v4 无序,列表按时间排要另存时间戳。

动手试试

生成标识符:UUID 生成。

实战案例:三个标识符设计失误

  1. 用自增 ID 做对外标识:/order/1024 直接暴露业务量,还能被遍历抓取。对外改用 UUID / ULID,内部仍保留自增主键。
  2. 把 NanoID 当密码用:NanoID 是随机标识符而非密钥,默认字符集与长度不足以承载秘密;令牌应使用足够熵的密码学随机值并设置过期。
  3. 忽略大小写与排序:入库时转成小写,查询却用大小写混写导致查不到;若需要按时间排序,应选 ULID 而不是 v4。

常见问题(FAQ)

UUID v4 和 ULID 怎么选?需要按生成时间排序、希望更短可读时选 ULID;纯随机且无排序需求选 v4。UUID 会泄漏信息吗?v4 不会;v1 编码了 MAC 地址与时间戳,可能泄漏机器信息。能直接做数据库主键吗?可以;但完全随机的 v4 会造成索引随机写,写入密集场景更宜用有序的 ULID。重复概率真的可以忽略吗?122 位随机下碰撞概率极低,但分布式唯一性仍建议加唯一索引兜底。

生成与存储的实操要点

  1. 生成源:必须用密码学安全随机数(浏览器 crypto.randomUUID() / getRandomValues,Node crypto.randomUUID()),不要用 Math.random;
  2. 存储类型:UUID 在 PostgreSQL 用 uuid 类型(16 字节)比 varchar(36) 省一半空间;MySQL 可用 BINARY(16);
  3. 索引与写入:随机主键会造成页分裂与随机写;写入密集的表建议用 ULID 这类有时间前缀的有序 ID,或保留自增主键、UUID 只做业务唯一键;
  4. 大小写与格式:入库前统一转小写并去掉连字符,避免“视觉相同但比较不等”;
  5. 长度校验:接收外部 ID 时先用正则校验格式再查库,避免无效值直接打到索引。

对外暴露与隐私

对外标识建议使用无序值,避免暴露业务量(/order/1024 会告诉别人你有多少订单)与被遍历抓取。反过来,内部日志与排查更依赖可排序 ID,因此常见做法是:内部主键自增、对外暴露 UUID/ULID,二者用映射表关联。

常见误区

  • 把 ULID 当“加密过的自增 ID”:它只编码时间,不含秘密,仍需鉴权;
  • 用短 ID 做安全边界:短码可被枚举,必须配合速率限制与权限校验;
  • 依赖“不会重复”而不建唯一索引:分布式系统里唯一约束是必需兜底;
  • 混用多种 ID 却无文档:同一个资源在不同接口用不同 ID 形式,会让调用方反复踩坑,应统一并写进文档。

ID 在 URL 与日志中的取舍

  • URL 中优先短且 URL 安全:NanoID 或去连字符的十六进制比带花括号的 UUID 更友好;
  • 日志中要可搜索:打印完整 ID 便于精确检索,截断会导致无法关联;
  • 不要把 ID 当权限:知道 ID 不等于有权访问,鉴权必须独立判断;
  • 敏感场景避免可枚举:顺序号可被遍历,若必须用顺序值,应配合访问控制与限速。

可读性与纠错

需要人工抄写或口述的 ID,应去掉易混淆字符(0/O、1/l/I)并使用分组显示;NanoID 支持自定义字符集正好可用。若必须人工输入,建议加校验位,能把绝大部分抄错在提交前拦下。

批量生成的注意点

一次生成大量 ID 时,注意两点:一是不要用同一个时间戳做多份 ULID 的时间前缀(同毫秒内应使用递增随机部分),否则排序会退化;二是生成后应做去重校验,尤其在多实例并发生成时,唯一索引是最后防线。