自增主键 vs UUID 主键对比分析

自增主键 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
喜欢就支持一下吧
点赞5 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容