
MySQL 中使用 UUID 作为主键确实会带来显著的性能隐患,核心问题不在 UUID 本身,而在于它与 InnoDB 的聚簇索引机制严重冲突。
UUID 导致页分裂和随机写入
InnoDB 表默认按主键顺序物理存储数据(聚簇索引)。自增 ID 是递增的,新记录总追加到索引末尾,写入高效、页利用率高。而 UUID(尤其是标准 v4)是完全随机生成的 128 位字符串,插入时几乎总是落在索引中间位置,迫使 InnoDB 频繁做页分裂、合并和数据移动。这不仅大幅增加 I/O 和 CPU 开销,还会导致 B+ 树深度异常增长、索引碎片严重。
- 实测场景:百万级表中,UUID 主键的 INSERT 吞吐量常比自增主键低 3–5 倍
- 后果:buffer pool 缓存命中率下降,磁盘随机读写激增,慢查询日志中大量“insert … on duplicate key update”类语句变慢
字符串类型放大存储与比较开销
UUID 通常以 CHAR(36) 或 VARCHAR(36) 存储(含 4 个连字符),实际占用 36 字节;即使去掉连字符用 BINARY(16),也比 BIGINT(8) 大一倍。更大的主键意味着:
- 二级索引中每条记录都冗余存储该主键值 → 索引体积膨胀,内存和磁盘压力双升
- JOIN、ORDER BY、GROUP BY 涉及主键时,字符串比较比整数比较慢得多(尤其带字符集和排序规则时)
- 网络传输和日志(binlog、redo log)体积增大,复制延迟风险上升
非顺序 UUID 的替代方案
若必须用 UUID 类标识符(如分布式系统防冲突),可规避随机性问题:
- UUID v1/v2(时间戳前缀):将时间信息放在高位,保证大致有序,但需注意时钟回拨和多节点 MAC 地址冲突
- ULID(Universally Unique Lexicographically Sortable Identifier):128 位,前 48 位为毫秒级时间戳,后 80 位为随机数,天然支持字符串字典序排序,可直接存为 CHAR(26) 或 BINARY(16)
- Snowflake 变种(如 Twitter Snowflake 或 Leaf-segment):64 位整数,含时间戳+机器ID+序列号,紧凑且严格递增/近序,推荐用 BIGINT UNSIGNED 存储
设计建议与迁移提示
除非业务强依赖 UUID 的全局唯一性和无状态生成能力(如离线设备预生成 ID),否则优先选择自增主键 + 唯一业务字段(如 order_no)组合。若已用 UUID 主键且性能恶化:
- 紧急优化:对大表执行 OPTIMIZE TABLE 整理碎片(需锁表或在线 DDL 支持)
- 长期方案:新增自增列作主键,原 UUID 列改为 UNIQUE NOT NULL,应用层逐步切换外键引用
- 新建表务必避免 CHAR(36) UUID;如需保留,至少用 BINARY(16) 存储,并用 UNHEX(REPLACE(uuid, '-', '')) 写入











