Pythonuuid与唯一标识
Python uuid 与唯一标识
==============================
uuid 模块生成统一唯一标识符 (UUID), 有多种版本满足不同场景。
1. uuid1 — 基于时间的 UUID
-----------------------------
import uuid
# UUID1: 基于当前时间戳 + 节点(MAC地址) + 时钟序列
# 优势: 时间有序, 可追踪生成时间
# 劣势: 会暴露 MAC 地址 (隐私问题)
u1 = uuid.uuid1()
print("UUID1:", u1)
print("UUID1 十六进制:", u1.hex) # 32 字符十六进制
print("UUID1 版本:", u1.version) # 1
# UUID1 字段解析
print("时间戳字段:", u1.time) # 100纳秒为单位的时间戳
print("时钟序列:", u1.clock_seq) # 时钟序列 (14位)
print("节点(MAC):", u1.node) # 48位 MAC 地址
# 自定义节点: 避免暴露真实 MAC 地址
custom_node = 0x123456789ABC
u1_custom = uuid.uuid1(node=custom_node)
print("自定义节点 UUID1:", u1_custom)
# 自定义时钟序列
u1_clock = uuid.uuid1(clock_seq=0x3FFF) # 时钟序列范围 0-0x3FFF
print("自定义时钟 UUID1:", u1_clock)
# 从 UUID1 提取时间
from datetime import datetime, timezone
time_100ns = u1.time - 0x01b21dd213814000 # UUID1 时间的偏移量
timestamp_sec = time_100ns / 10000000
dt = datetime.fromtimestamp(timestamp_sec, tz=timezone.utc)
print("UUID1 生成时间 (UTC):", dt)
2. uuid4 — 随机 UUID
----------------------
import uuid
# UUID4: 完全随机生成 (122位随机数 + 6位版本/变体位)
# 优势: 无隐私泄露, 适合大多数应用场景
# 劣势: 无序, 数据库索引性能稍差
u4 = uuid.uuid4()
print("UUID4:", u4)
print("UUID4 版本:", u4.version) # 4
print("UUID4 字节:", u4.bytes) # 16字节原始数据
print("UUID4 整数表示:", u4.int) # 128位整数
# 生成批量 UUID (列表推导式)
batch = [uuid.uuid4() for _ in range(5)]
print("批量 UUID4:")
for i, uid in enumerate(batch):
print(f" {i+1}: {uid}")
# UUID4 碰撞概率: 极其微小
# 每秒生成 10 亿个 UUID4, 约 100 年后才可能发生一次碰撞
# 公式: 碰撞概率 ~ n^2 / (2 * 2^122)
3. uuid3 / uuid5 — 基于名称的 UUID
--------------------------------------
import uuid
# UUID3: 基于命名空间 + 名称的 MD5 哈希 (128位)
# UUID5: 基于命名空间 + 名称的 SHA-1 哈希 (128位)
# 预定义命名空间:
# NAMESPACE_DNS — 域名
# NAMESPACE_URL — URL
# NAMESPACE_OID — ISO OID
# NAMESPACE_X500 — X.500 DN
# UUID5: SHA-1 版 (推荐)
ns_dns = uuid.NAMESPACE_DNS
u5_domain = uuid.uuid5(ns_dns, "example.com")
print("UUID5 (DNS):", u5_domain) # 对 example.com 的一致 UUID
# 同一名称在同一命名空间下总是生成相同 UUID
u5_domain2 = uuid.uuid5(ns_dns, "example.com")
print("UUID5 相同:", u5_domain == u5_domain2) # True
# UUID3: MD5 版 (兼容旧系统)
u3_domain = uuid.uuid3(ns_dns, "example.com")
print("UUID3 (DNS):", u3_domain)
print("UUID3 长度:", len(u3_domain.hex)) # 32
# 不同命名空间下相同名称不同结果
u5_url = uuid.uuid5(uuid.NAMESPACE_URL, "example.com")
print("UUID5 (URL):", u5_url) # 与 DNS 版不同
# 实用场景: 为资源生成确定性 UUID
def resource_uuid(resource_type, resource_id):
"""为资源生成稳定的 UUID5 标识"""
ns = uuid.uuid5(uuid.NAMESPACE_DNS, "myapp.example.com")
return uuid.uuid5(ns, f"{resource_type}:{resource_id}")
user_uuid = resource_uuid("user", "alice")
post_uuid = resource_uuid("post", "12345")
print("用户 UUID:", user_uuid)
print("文章 UUID:", post_uuid)
4. UUID 对象字段与操作
-------------------------
import uuid
u = uuid.uuid4()
# 基本字段
print("UUID 字符串:", str(u)) # 标准格式: 8-4-4-4-12
print("UUID hex:", u.hex) # 32字符十六进制
print("UUID 字节:", u.bytes) # 16字节 (bytes)
print("UUID 字节序 LE:", u.bytes_le) # 混合字节序 (小端 time 域)
print("UUID int:", u.int) # 128位整数表示
print("UUID 变体:", u.variant) # RFC 4122
print("UUID 版本:", u.version) # 4
# UUID 比较和排序
u1 = uuid.uuid4()
u2 = uuid.uuid4()
print("u1 < u2:", u1 < u2) # 按整数值比较
print("相等判断:", u1 == u1) # True
# 从不同格式创建 UUID
u_from_hex = uuid.UUID(hex="550e8400e29b41d4a716446655440000")
u_from_bytes = uuid.UUID(bytes=b"\x55\x0e\x84\x00\xe2\x9b\x41\xd4\xa7\x16\x44\x66\x55\x44\x00\x00")
u_from_int = uuid.UUID(int=0x550e8400e29b41d4a716446655440000)
print("从 hex 创建:", u_from_hex)
print("从 bytes 创建:", u_from_bytes)
print("从 int 创建:", u_from_int)
# UUID 的不可变性
u_frozen = uuid.uuid4()
# u_frozen.int = 123 # 会报错! UUID 不可变
# 将 UUID 用于字典键
cache = {uuid.uuid4(): "value1", uuid.uuid4(): "value2"}
print("UUID 作为字典键:", len(cache))
5. UUID 与数据库主键策略
----------------------------
import uuid
# 场景: 使用 UUID 作为分布式数据库主键
# 优势: 全局唯一, 无需中心化序列, 安全 (不暴露数据量)
# 劣势: 16字节 vs 4字节 int, 索引效率较低
# 策略1: 直接用 UUID 字符串
class ModelWithUUID:
def __init__(self, name: str):
self.id = uuid.uuid4()
self.name = name
def to_db(self):
"""存储到数据库: UUID 转 hex 或 bytes"""
return {
"id_hex": self.id.hex, # 32 字符字符串
"id_bytes": self.id.bytes, # 16 字节 (需 BINARY(16) 类型)
"id_str": str(self.id), # 36 字符字符串
}
entity = ModelWithUUID("测试数据")
print("数据库存储准备:", entity.to_db())
# 策略2: 有序 UUID (类似 UUID1 但更安全)
# 可结合时间戳 + 随机数自行生成有序 UUID
import time
import random
def ordered_uuid():
"""生成时间有序的 UUID (类似 UUID7 方案)"""
# 自定义: 毫秒时间戳(高48位) + 随机数(低80位)
timestamp_ms = int(time.time() * 1000)
rand_bits = random.getrandbits(80)
# 组合成 128 位 UUID
value = (timestamp_ms << 80) | rand_bits
# 设置版本位 (4) 和变体位
value &= ~(0xC000 << 64) # 清除变体位
value |= 0x8000 << 64 # 设置变体 (RFC 4122)
value &= ~(0xF000 << 76) # 清除版本位
value |= 4 << 76 # 设置版本 4
return uuid.UUID(int=value)
print("有序 UUID:", ordered_uuid())
# 策略3: PostgreSQL 的 uuid-ossp 扩展兼容
# PSQL: CREATE EXTENSION "uuid-ossp";
# uuid_generate_v4() -> UUID 类型列
6. Snowflake ID 模式
-------------------------
import time
import threading
class SnowflakeGenerator:
"""简易雪花 ID 生成器 (64位整数, 非 UUID)"""
def __init__(self, worker_id: int, datacenter_id: int, sequence: int = 0):
self.worker_id = worker_id & 0x1F # 5位 工作节点ID
self.datacenter_id = datacenter_id & 0x1F # 5位 数据中心ID
self.sequence = sequence & 0xFFF # 12位 序列号
self.tw_epoch = 1609459200000 # 自定义纪元 (2021-01-01)
self.last_timestamp = -1
self.lock = threading.Lock()
def _current_millis(self) -> int:
return int(time.time() * 1000)
def _wait_next_millis(self, last_ts: int) -> int:
ts = self._current_millis()
while ts <= last_ts:
ts = self._current_millis()
return ts
def next_id(self) -> int:
"""生成下一个 64 位雪花 ID"""
with self.lock:
timestamp = self._current_millis()
# 时钟回拨处理 (简化版, 仅等待)
if timestamp < self.last_timestamp:
timestamp = self._wait_next_millis(self.last_timestamp)
if timestamp == self.last_timestamp:
self.sequence = (self.sequence + 1) & 0xFFF
if self.sequence == 0: # 序列号用完
timestamp = self._wait_next_millis(self.last_timestamp)
else:
self.sequence = 0
self.last_timestamp = timestamp
# 组合: 时间戳(41位) + 数据中心(5位) + 工作节点(5位) + 序列号(12位)
snowflake_id = (
((timestamp - self.tw_epoch) << 22) |
(self.datacenter_id << 17) |
(self.worker_id << 12) |
self.sequence
)
return snowflake_id
def parse(self, snowflake_id: int) -> dict:
"""解析雪花 ID 中的各字段"""
sequence = snowflake_id & 0xFFF
worker_id = (snowflake_id >> 12) & 0x1F
datacenter_id = (snowflake_id >> 17) & 0x1F
timestamp = (snowflake_id >> 22) + self.tw_epoch
return {
"id": snowflake_id,
"timestamp_ms": timestamp,
"datacenter_id": datacenter_id,
"worker_id": worker_id,
"sequence": sequence,
}
# 测试雪花 ID
snowflake = SnowflakeGenerator(worker_id=1, datacenter_id=0)
ids = [snowflake.next_id() for _ in range(3)]
print("雪花 ID 列表:", ids)
for sid in ids:
info = snowflake.parse(sid)
dt = datetime.fromtimestamp(info["timestamp_ms"] / 1000, tz=timezone.utc)
print(f" ID={sid}, 时间={dt}, 数据中心={info['datacenter_id']}, 节点={info['worker_id']}, 序列={info['sequence']}")
7. UUID 的转换与编码
-----------------------
import uuid
# UUID 转不同格式
u = uuid.uuid4()
# 标准 36 字符格式
print("标准格式:", str(u)) # xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# 32 字符十六进制 (不带连字符)
print("Hex 格式:", u.hex) # 32 字符
# 16 字节 (二进制紧凑格式)
print("Bytes 格式:", u.bytes) # 16 字节
# Base64 编码 (更紧凑的字符串表示, 22 字符)
import base64
base64_str = base64.urlsafe_b64encode(u.bytes).rstrip(b"=").decode()
print("Base64 格式:", base64_str) # 22 字符
# Base64 解码回 UUID
decoded_bytes = base64.urlsafe_b64decode(base64_str + "==")
restored_uuid = uuid.UUID(bytes=decoded_bytes)
print("恢复 UUID:", restored_uuid)
print("一致?", u == restored_uuid)
# 短 UUID 方案: Base62 编码
BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
def uuid_to_base62(uid: uuid.UUID) -> str:
"""将 UUID 编码为 Base62 字符串 (约 22 字符)"""
num = uid.int
if num == 0:
return BASE62[0]
result = []
while num > 0:
result.append(BASE62[num % 62])
num //= 62
return "".join(reversed(result))
short_id = uuid_to_base62(u)
print("Base62 短 ID:", short_id)
print("长度:", len(short_id)) # 约 22 字符
总结: uuid4 最适合通用场景; uuid5 用于确定性标识; uuid1 用于时间有序; 雪花 ID 适合高性能分布式系统.
