分布式场景下必须禁用数据库自增id,改用雪花id等全局唯一方案;uuid因无序性导致页分裂,性能差3–5倍;自然键作主键易引发级联更新和性能问题。

自增 ID 在分布式写入时会直接卡死
单库单表用 auto_increment 最省心,但一旦分库分表或接入多个写节点,ID 生成就变成中心瓶颈。MySQL 的 auto_increment_offset 和 auto_increment_increment 只能做简单轮询,无法应对动态扩缩容;PostgreSQL 的 serial 更是完全不支持跨实例协调。
常见错误现象:Duplicate entry 'X' for key 'PRIMARY' 或写入延迟飙升,本质是应用层抢锁或代理层(如 ProxySQL、ShardingSphere)没正确透传自增策略。
- 只在单主 MySQL + 无分片场景下放心用
auto_increment - 分库后必须禁用所有节点的
auto_increment,改由中间件或应用层统一分配 - PostgreSQL 若用
IDENTITY列,务必确认是否启用了GENERATED ALWAYS—— 否则 COPY 或批量插入可能绕过生成逻辑
UUID v4 写性能差不是因为长度,而是页分裂
UUID 字符串本身占 36 字节(含连字符),但真正拖慢的是无序性:新记录的 UUID 值随机散落在 B+ 树各处,导致频繁页分裂、缓存命中率骤降。实测 MySQL 下,同等数据量插入吞吐比自增 ID 低 3–5 倍。
使用场景:适合只读多、写入少,且需要客户端离线生成 ID 的场景(比如移动端离线草稿同步)。
- 务必用
BINARY(16)存储,而不是VARCHAR(36)—— 能省一半空间,索引效率提升明显 - 避免用
UUID_SHORT():它依赖服务器启动时间戳,集群中多实例部署极易重复 - PostgreSQL 的
gen_random_uuid()需要pgcrypto扩展,别漏装
雪花 ID 必须自己校验时间回拨,数据库不帮你兜底
雪花 ID 的 64 位结构里,时间戳占 41 位,精度为毫秒。一旦机器时钟回拨(NTP 同步异常、运维误操作),worker_id 相同的节点就会生成重复 ID。MySQL 和 PostgreSQL 都不会拦截这种重复,只会抛出 Duplicate entry 错误。
参数差异:epoch 起始时间选错会导致 ID 过早溢出(如用 Unix epoch 1970 年,41 位只能撑到 2039 年);worker_id 分配若依赖 ZooKeeper 或 etcd,要额外处理节点上下线时的 ID 段回收。
- 应用层生成 ID 后,必须先查一次数据库确认唯一性,再执行 INSERT —— 尤其在金融类强一致性场景
- 别把
worker_id硬编码进配置,应从服务发现组件动态获取,否则扩容时容易冲突 - MySQL 8.0+ 支持函数索引,可用
CREATE INDEX idx_snowflake_ts ON t1 ((CAST((id >> 22) AS UNSIGNED)))加速按时间范围查询
自然键当主键?先看更新频率和业务耦合度
用邮箱、身份证号、订单号这类业务字段做主键,表面省了 ID 列,实际埋雷:一旦邮箱改绑、身份证升位、订单号规则变更,整个外键链路全得跟着级联更新。而且这些字段通常带语义,索引体积大、比较慢。
性能影响:MySQL 的二级索引叶子节点存的是主键值,自然键越长,二级索引越大;PostgreSQL 的 TOAST 机制虽能压缩大字段,但主键仍需全程参与排序和连接。
- 仅当该字段绝对不可变、全局唯一、且长度 ≤ 32 字节时才考虑(比如 ISO 国家代码
CHAR(2)) - 订单号若含日期前缀(如
20260415-XXXX),时间局部性好,但仍是字符串比较,不如整型快 - 千万别用 JSON 字段或逗号分隔字符串当自然键 —— 即使当前唯一,后续扩展性归零
最麻烦的其实是迁移成本:现有系统想从自然键切到雪花 ID,不只是改主键,所有关联表的外键、索引、应用层 DTO、缓存 key 设计都得动。这事没有银弹,得一行行代码核对。










