自增id能避免页分裂,因其插入总在最右叶子页末尾,页满时仅分配新页、无需数据迁移;uuid因随机性导致频繁中间页插入,触发页分裂并引发i/o放大与索引膨胀。

自增主键不是“更合适”,而是唯一能让InnoDB的B+树聚簇索引按设计预期高效运转的主键类型;UUID强行塞进去,等于让一辆跑车在泥地里全速过弯——结构没坏,但所有性能优势都被物理规律抵消了。
自增ID如何让B+树插入不触发页分裂
InnoDB的聚簇索引就是数据本身,B+树叶子节点必须严格按主键值升序链接。自增ID(如BIGINT AUTO_INCREMENT)每次插入都落在最右端页末尾,只要该页还有空闲空间(默认填充因子15/16),就直接追加,完全不动其他页。
-
SHOW TABLE STATUS LIKE 'users'里的Data_free通常低于100KB,说明页利用率高、碎片少 - 而UUID是随机128位值,90%以上插入点落在中间页——目标页一满,立刻触发
page split:搬走约50%键值、新建页、更新父节点指针 - 被挤出的页可能已刷盘,又要重新读回Buffer Pool,I/O放大不可忽视
UUID为何让二级索引体积翻倍甚至更多
InnoDB所有二级索引的叶子节点里存的不是行指针,而是**完整主键值**。主键长度直接乘以行数,就是二级索引额外开销。
-
BIGINT主键只占8字节;CHAR(36)存标准UUID(含横杠)实际占36字节,是前者的4.5倍 - 1000万行表,仅二级索引就多占上百MB;Buffer Pool缓存命中率暴跌,
Innodb_buffer_pool_reads持续上升就是信号 - 用
BINARY(16)+UUID_TO_BIN(UUID(), TRUE)能省到16字节,但仍是BIGINT的2倍,无法根治膨胀
为什么UUID主键下WHERE id BETWEEN ...会变慢
范围查询快不快,不取决于有没有索引,而取决于B+树叶子节点在磁盘上的物理连续性。
- 自增ID下,
id BETWEEN 1000 AND 2000大概率落在连续几个数据页里,顺序I/O搞定 - UUID下,这些“逻辑相邻”的ID在磁盘上可能散落在数百个不连续页中,变成大量随机I/O
- 执行计划里看到
type: ALL或type: index而不是type: range,基本可判定物理存储已打散 - 应用层传参若漏横杠、大小写混用(如
'9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d'),索引直接失效
ALTER TABLE改主键不是改个类型那么简单
线上表想把UUID主键切回自增ID,ALTER TABLE users MODIFY id BIGINT AUTO_INCREMENT几乎必失败。
- InnoDB必须重建整个聚簇索引,千万级表锁表常超20分钟
- 若有外键引用(比如
orders.order_id是VARCHAR(36)并REFERENCES users(id)),直接报错ERROR 1832 (HY000) - 二级索引、复制延迟、触发器都会被牵连,迁移成本远高于初期选型
- 安全路径是:新增
new_id BIGINT AUTO_INCREMENT列 → 批量填充 → 停写 → 切换应用 → 删除旧列
真正容易被忽略的,是页分裂带来的隐性I/O代价:它不体现在慢日志的Query_time里,却让Buffer Pool里刚热起来的页被频繁挤出,后续查询被迫反复从磁盘加载——这种衰减是渐进的、隐蔽的,等你注意到时,往往已经在线上跑了半年UUID主键表。











