2、UUID生成:UUID版本(v1/v4/v5)、Python uuid模块、UUID的优缺点分析

好,咱们来聊聊UUID。这东西在分布式系统里几乎每天都在用。我最早接触UUID是在做一个跨机房的样本采集系统,当时主键冲突搞得我焦头烂额,后来换了UUID,整个世界清净了——嗯,但也不是万能的。

2.1 UUID版本:v1 / v4 / v5,到底选哪个?

UUID(Universally Unique Identifier)一共5个版本,但真正干活的就是v1、v4、v5。咱们一个一个来过。

v1:基于时间+MAC地址

v1把当前时间戳(100纳秒粒度)和机器的MAC地址拼在一起。这东西有个特点:同一台机器上生成的v1 UUID按顺序递增。你想想看,这适合做数据库主键吗?适合,因为不用随机IO。但我提醒你——MAC地址暴露了,有安全风险。

我曾经在乙方项目里帮客户做数据脱敏,结果发现日志里记录的UUID全是v1,反查MAC能定位到具体服务器。客户脸都绿了。从那以后,只要涉及外发数据,我绝不用v1。

v4:完全随机

v4用随机数(或伪随机)填充122位,剩下6位固定。碰撞概率?理论上是2⁻¹²²,约等于5.3×10³⁷个里才可能撞一次。实际项目中我生成过几十亿条记录,一次都没撞过。放心用。

v4的缺点就是无序。如果你把v4 UUID当MySQL主键,插入时会频繁触发页分裂,磁盘写入变慢。这个我在后面“抗量设计”章节会详细说——但今天先记住:高频写入场景别用v4做主键。

v5:基于命名空间+名称的SHA-1哈希

v5需要两个输入:命名空间UUID + 任意字符串。同样的命名空间和名称,永远输出同一个UUID。这个特性非常适合样本去重——比如你有一个患者ID+样本类型,可以生成一个确定性的唯一标识。

我遇到过一个问题:试剂的批次号每天变,但样本本身不变。如果用v5把“样本ID+批次号”哈希,每次批次变动都会生成新UUID,导致去重失败。后来改用“样本ID+采集日期”作为名称,才稳定下来。

一句话选型: 安全场合用v4,排序需求用v1但小心隐私,确定性去重用v5。

2.2 Python uuid模块:开箱即用

Python标准库直接带了uuid模块,不用装任何第三方包。我用它写了无数脚本,从来没掉过链子。下面给出三个版本的具体用法。

import uuid

# v1:基于时间
uid_v1 = uuid.uuid1()
print(uid_v1)          # 例如: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
print(uid_v1.time)     # 内部时间戳
print(uid_v1.node)     # 网络节点(MAC地址)

# v4:随机
uid_v4 = uuid.uuid4()
print(uid_v4)          # 例如: 6fa459ea-f403-4b9a-9e65-9b1e8a9b8c9d

# v5:基于命名空间+名称
namespace = uuid.NAMESPACE_DNS
name = "sample_1234_blood_20250301"
uid_v5 = uuid.uuid5(namespace, name)
print(uid_v5)          # 每次运行结果相同
小技巧: 生成v5时,命名空间可以用您自己的基UUID,不一定非用预定义的。我习惯把项目代码的UUID设为命名空间,这样同项目内名称空间是唯一的。

如果你要把UUID转成字符串,直接str(uid)就搞定了。想拿纯数字?uid.int返回一个128位的整数。这在做分库分表路由时特别有用。

2.3 UUID的优缺点分析

别被“唯一”两个字骗了——UUID不是银弹。我做技术选型时,会把优缺点列成一张表贴在项目文档里。你直接拿去用。

维度 优点 缺点
唯一性 全球唯一,不依赖中央节点 v4理论上仍有极低碰撞概率(基本可忽略)
性能 生成速度快(纳秒级) v4做数据库主键时乱序,导致B+树频繁分裂
存储 定长16字节,可存BINARY(16) 字符串形式36字符,是自增id的3~4倍
可读性 无;纯机器识别 无法排序、无法目视区分先后
密钥空间 128位,非常大 v1暴露MAC,v5存在确定性回溯风险

你想想看,一个36字符的字符串在MySQL里做索引,每个索引页能装入的记录数量会大幅下降。我记得有一次优化存量表,把UUID从VARCHAR(36)改成BINARY(16)之后,表容量下降了40%,查询速度提升了约25%。这就是存储代价。

坑: 不要在Python里用uuid.uuid1().hex存到数据库——你丢失了时间字段信息,但MAC地址仍然在。如果非要存,至少把node字段单独摘出来脱敏。

2.4 一张图说清UUID选型逻辑

下面这张图我手绘了很久才定稿。它把三个版本的适用场景、核心风险、Python生成方式串在一起了。

UUID版本选型逻辑 需要确定性? 是 否 v5:SHA-1哈希 命名空间+名称 → 确定UUID 需要排序? 样本去重/确定性 是 隐私关注? v4:随机 安全但无序 v1:时间戳+MAC 有序但暴露硬件 💡 说明:如果只需要排序但能接受MAC暴露,v1是首选;无法接受MAC暴露则必须用v4(放弃排序);确定性场景直接走v5。

2.5 我的个人实践建议

  • 样本标识首选v5:我自己的样本采集系统,用“源系统ID+采集时间戳(毫秒)”做名称,配合固定命名空间UUID。这样同一个样本无论重复上报多少次,UUID都不变,天然去重。
  • 日志追踪用v4:日志里不需要排序,也不希望任何人反向猜测ID趋势。v4最适合。
  • 数据库主键用v1但脱敏MAC:如果非要让主键有序,可以自己包装一层——取v1的时间部分,再拼一段随机数。Python里可以这么干:
import uuid, time

def ordered_uuid():
    uid = uuid.uuid1()
    # 只保留时间高位,最后4个字节用随机替换(去掉MAC)
    mask = 0xffffffffffffffffffffffff00000000
    return uuid.UUID(int=(uid.int & mask) | (uuid.uuid4().int & 0x00000000ffffffff))

print(ordered_uuid())   # 有序 + 不暴露MAC
核心总结: UUID不是万能药,但你只要搞懂v1/v4/v5的区别,再配合Python内置模块,90%的标识场景都能优雅解决。剩下10%?那是定制方案的事,后面的章节我会讲到。