mysql主键推荐自增id而非uuid,因其避免页分裂、提升索引效率与磁盘局部性;uuid随机插入导致b+树频繁分裂、碎片率高、二级索引膨胀、范围查询退化为随机io,且存储和比较开销显著高于整型。

MySQL中主键字段推荐用自增ID,不是因为UUID“不唯一”或“不能用”,而是它在InnoDB引擎下会直接拖垮写入性能、索引效率和磁盘局部性——尤其当数据量超过十万行后,差距肉眼可见。
自增ID插入不触发页分裂,UUID会高频分裂
InnoDB的聚簇索引把行数据按主键顺序存进B+树叶子节点,每个页默认16KB。自增ID(如BIGINT AUTO_INCREMENT)总是在末尾追加,新页顺序分配,几乎不拆分旧页;而UUID是随机128位值,每次插入位置不可预测,B+树必须频繁做节点拆分、合并、指针重连。实测百万级写入,UUID主键表的Data_free值(SHOW TABLE STATUS LIKE 'users'查到)通常是自增表的3倍以上,说明碎片严重。
- 页分裂不仅耗CPU,还放大随机IO,影响刷脏页和缓冲池命中率
- 分裂后的空闲空间无法被新插入的UUID有效利用,进一步加剧碎片
- 如果表有二级索引,每个二级索引叶子节点都存主键值——UUID主键会让所有二级索引体积暴涨
UUID主键让范围查询退化成全表扫描
WHERE id BETWEEN 'a' AND 'b' 这类查询,在自增ID下能高效走索引范围扫描,读取连续几个数据页;但UUID主键下,“逻辑相邻”的ID在物理存储上可能散落在数百个不同页里,变成大量随机IO。更隐蔽的问题是:ORM或应用层传参稍有偏差(比如漏横杠、大小写混用、多空格),WHERE id = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d' 就无法命中索引,执行计划里type会从const掉到ALL。
- 检查方法:
EXPLAIN SELECT * FROM users WHERE id = 'xxx',确认type是const或range -
CHAR(36)字段不能接受REPLACE(uuid, '-', '')生成的32位字符串,否则索引失效 - 联合索引最左前缀原则下,若第一列是UUID(如
(user_id, created_at)),整个索引的范围能力基本归零
UUID存储和比较开销远高于整型
一个BIGINT占8字节,而原生UUID字符串(带横杠)在utf8mb4下实际占36字符×3字节=108字节,加上长度头,超110字节;即使转成BINARY(16),也比整型多一倍空间。B+树每个节点能存的键值数量直接受主键长度影响——主键越长,单页能存的键越少,树高增加,跨页遍历次数上升。
- CPU比较
8字节整数是一次指令完成;比较UUID需逐字符ASCII比对,慢数倍到数十倍 - MySQL 8.0+可用
UUID_TO_BIN(UUID(), TRUE)把时间戳高位前置,提升局部性,但仍是补救,非根治 - 别用
UUID()原生函数结果建主键——它返回带横杠的CHAR(36),既浪费空间又拖慢排序
ALTER TABLE改主键代价远超预期
线上表想从UUID主键切回自增ID,ALTER TABLE users MODIFY id BIGINT AUTO_INCREMENT大概率失败。这不是语法问题,而是InnoDB要重建整个聚簇索引——千万级表锁表时间常超20分钟,且外键约束、二级索引、触发器全受影响。若其他表有FOREIGN KEY (order_id) REFERENCES users(id),而order_id仍是VARCHAR(36),直接改主键会报错ERROR 1832 (HY000): Cannot change column 'id': used in a foreign key constraint。
- 真要换,得先删外键、改关联字段类型、重建索引、再改主键,步骤多且不可逆
- 更现实的做法是加冗余字段:
external_id CHAR(36)存UUID用于业务对接,主键仍用id BIGINT AUTO_INCREMENT - 分布式场景下需要全局唯一ID,优先考虑应用层生成雪花ID并作为普通字段,而非主键
真正容易被忽略的点是:问题往往不出在“能不能用UUID”,而出在“是否意识到UUID主键会让所有二级索引、所有JOIN、所有ORDER BY、所有缓存局部性同步劣化”。一旦表增长到几十万行,这些隐性成本会集中爆发,而那时再重构,代价已远超初期选型多花的五分钟。











