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+采集日期”作为名称,才稳定下来。
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) # 每次运行结果相同
如果你要把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%。这就是存储代价。
uuid.uuid1().hex存到数据库——你丢失了时间字段信息,但MAC地址仍然在。如果非要存,至少把node字段单独摘出来脱敏。
2.4 一张图说清UUID选型逻辑
下面这张图我手绘了很久才定稿。它把三个版本的适用场景、核心风险、Python生成方式串在一起了。
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