自增主键优于UUID主键:自增ID顺序插入避免页分裂、碎片少、IO高效;UUID随机插入导致高频页分裂、碎片率高、索引失效风险大,且优化难根本解决。

因为InnoDB聚簇索引的物理结构决定了:自增主键天然适配B+树尾部追加,而UUID无论怎么优化都难逃随机插入引发的页分裂、碎片和随机IO——这不是调优能解决的问题,是存储引擎层面的硬约束。
自增ID插入时几乎不触发页分裂
自增主键(如BIGINT AUTO_INCREMENT)每次插入都落在B+树最右端的数据页末尾,只要页有空闲空间,就直接追加;即使需要新页,也是顺序分配。实测百万级写入后,Data_free通常低于100KB,页利用率稳定在80%以上。
- 页分裂频率趋近于0,I/O以顺序写为主
- Buffer Pool缓存局部性好,相邻ID大概率在同一页或邻近页
-
ORDER BY id、WHERE id BETWEEN ? AND ?能高效走索引范围扫描
UUID主键必然导致高频页分裂
标准UUID()生成的是128位随机字符串(如'550e8400-e29b-41d4-a716-446655440000'),B+树必须二分查找插入位置——90%以上概率落在已有页中间。一旦页满(16KB),InnoDB强制执行page split:搬出约50%键值、新建页、更新父节点指针。
- 实测每插入1000行UUID,平均触发1.7次页分裂
-
SHOW TABLE STATUS中Data_free持续攀升,碎片率常超30% - 单页存储量从自增ID的~100条暴跌至30–40条,浪费大量缓冲池和磁盘空间
改成BINARY(16)或UUID_TO_BIN()也没用根本问题
把CHAR(36)换成BINARY(16)只省了20字节存储,但B+树比较逻辑仍是按字节序列排序,而原生UUID字节序仍是时间戳+随机段混排,高位不单调——插入位置依然不可预测。
-
UUID_TO_BIN(UUID(), FALSE)只是无损压缩,不改变随机性 -
UUID_TO_BIN(UUID(), TRUE)(MySQL 8.0+)把时间戳高位前置,可降低约15%分裂频率,但相比自增ID仍差一个数量级 - 二级索引叶子节点存的是主键值,
BINARY(16)比BIGINT多占8字节,所有二级索引体积放大2倍以上
应用层传参稍错一点,索引就彻底失效
UUID字段定义为CHAR(36),但应用传参用了REPLACE(uuid, '-', '')得到的32位字符串,或者大小写混用、漏横杠,都会让MySQL无法命中索引。
-
EXPLAIN SELECT * FROM users WHERE id = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'里type显示ALL就是典型信号 - 联合索引如
(user_id, created_at),若user_id是UUID,整个索引的范围能力基本归零 - ORM默认把数字ID当字符串传(
WHERE id = '12345')对BIGINT还能隐式转换,但对UUID字段极易因格式不一致直接全表扫
真正棘手的不是“能不能用UUID”,而是它一旦成为主键,就锁死了聚簇索引的物理组织方式——后续所有优化都像在漏水的船上补洞。如果业务真需要分布式ID,优先考虑冗余字段(如external_id BINARY(16))而非强改主键。


















