自增主键 vs UUID 主键对比分析
问题引出
设计数据库表时,主键选择是一道经典选择题:
-- 自增主键
CREATE TABLE user_auto (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50)
);
-- UUID 主键
CREATE TABLE user_uuid (
id CHAR(36) PRIMARY KEY,
name VARCHAR(50)
);
哪个更好?答案取决于你的业务场景。
自增主键的优势
1. B+树写入性能高
自增主键本质上是顺序插入,新记录总是追加到 B+树的最右叶子节点:
B+树结构(顺序写入):
[1] → [2] → [3] → [4] → [5] → ...
↑ 新记录直接追加
- 无需频繁触发页分裂
- 索引树保持紧凑
- 写性能稳定
2. 存储空间小
自增 BIGINT:8 字节
UUID CHAR(36):36 字节(甚至更多)
二级索引主键占用:
自增 → 每个二级索引存 8 字节
UUID → 每个二级索引存 36 字节
数据量越大,差距越明显。
3. 内存效率高
- 自增主键连续 → B+树节点利用率高 → 缓存命中率高
- 范围查询快:
WHERE id BETWEEN 100 AND 200
UUID 主键的优势
1. 分布式场景友好
应用层生成 ID,无需依赖数据库:
- 分库分表不会冲突
- 合并数据不用处理 ID 冲突
- 多库独立生成,无中心瓶颈
2. 数据安全
自增暴露问题:
/user/1000 → 可推测过去有 999 条记录
/user/10000 → 每天约 9000 增长,可反推业务量
UUID 暴露问题:
/a1b2c3d4-... → 猜不出任何信息
3. 避免单点瓶颈
-- 自增在高并发下:
INSERT INTO t (name) VALUES ('A'); -- 等待自增锁
INSERT INTO t (name) VALUES ('B'); -- 排队
-- InnoDB 的 AUTO-INC 锁可能成为热点
-- UUID 直接生成,无锁竞争:
INSERT INTO t (id, name) VALUES (UUID(), 'A');
INSERT INTO t (id, name) VALUES (UUID(), 'B');
UUID 的致命缺陷
1. 随机写入导致页分裂
UUID 无序 → 插入位置随机 → 频繁页分裂:
B+树结构(随机写入):
[1] [5] [9] [3] [7] [2] ...
插入 6 → 在第 2 页和第 3 页之间 → 页分裂!
插入 4 → 在第 1 页和第 2 页之间 → 页分裂!
每次页分裂代价巨大:
– 分配新页
– 复制一半数据
– 更新父节点指针
– 产生碎片
2. 存储占用多
1000 万条数据:
自增 BIGINT: 80 MB(主键)
UUID CHAR(36): 360 MB(主键)
+ 二级索引中复制的 UUID → 总空间差距数倍
3. 随机 IO 性能低
自增主键写盘是顺序 IO,UUID 是随机 IO。机械硬盘下差距可达百倍。
折中方案
有序 UUID(UUID v7)
MySQL 8.0 支持 UUID v7,按时间排序生成:
SELECT UUID_TO_BIN(UUID(), true);
-- 生成 time-ordered 二进制 UUID
雪花算法(Snowflake)
-- 业务生成 64 位长整型
-- 时间戳(41) + 机器ID(10) + 序列号(12)
-- 有序递增,8 字节,分布式唯一
混合策略
-- 自增主键(内部) + UUID(业务)
CREATE TABLE t (
id BIGINT AUTO_INCREMENT PRIMARY KEY, -- 内部关联用
biz_id CHAR(36) UNIQUE, -- 对外暴露
UNIQUE INDEX uk_biz_id(biz_id)
);
自增主键的缺陷(需要知道的)
-- 1. 迁移麻烦:多库合并 ID 冲突
-- 库A 最后 ID:1000
-- 库B 最后 ID:1000
-- 合并后:ID 重复!
-- 2. 数据暴露
SELECT * FROM user ORDER BY id DESC LIMIT 1;
-- 今天 10000,明天 15000 → 每天 5000 新增
-- 3. 跳号问题
INSERT ... ROLLBACK; -- ID 被占用但回滚,不会重用
-- ID 序列:1, 2, 3, 4 (3被回滚了), 5, 6...
面试决策指南
| 业务场景 | 推荐主键 | 原因 |
|---|---|---|
| 单体应用,数据量 < 1 亿 | 自增 BIGINT | 写入性能最优 |
| 海量数据,写入密集型 | 自增 BIGINT | 避免页分裂 |
| 分库分表 | 雪花算法 / UUID v7 | 全局唯一 |
| 对外暴露 ID | 雪花算法 / 业务 ID | 安全 |
| 多源数据合并 | UUID 系 | 无冲突 |
生产者 vs 消费者的辨析
还有一个常被忽略的角度:谁生成 ID。
自增主键:数据库生成 → 写入后才能获取 ID
UUID: 应用生成 → 先有 ID 再写入
影响:
- 自增:需要写回才能拿到 ID,可能要多一次查询
- UUID:可以在代码里预先组装对象,写的时候直接 INSERT
面试要点
- 自增主键:写入性能最好、存储最小、适合单体应用
- UUID 主键:分布式友好、安全、但写入性能差(页分裂)
- 解决 UUID 问题:用雪花算法或 UUID v7 获得有序 ID
- 终极建议:大多数场景选自增 BIGINT,需要分布式时用雪花算法
- 不要选 CHAR(36) 的 UUID:MySQL 官方文档也建议避免随机 UUID 作主键
一句话总结:自增主键是默认选择,性能优秀;UUID 主键专治分布式 ID 冲突问题,务必用有序版本控制性能损耗。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容