UUID vs 自增 ID:数据库主键选择指南
选择数据库主键是数据库设计的核心决策。本文对比 UUID 和自增 ID 的优缺点。
对比总览
| 特性 |
自增 ID |
UUID |
| 唯一性 |
单库唯一 |
全局唯一 |
| 有序性 |
✅ 有序 |
❌ 无序(V4) |
| 存储大小 |
4-8 字节 |
16 字节 |
| 可预测性 |
可预测 |
不可预测 |
| 分布式支持 |
❌ 需要协调 |
✅ 无需协调 |
| 安全性 |
低(可遍历) |
高(不可遍历) |
| 性能 |
高 |
中 |
自增 ID
优势
- 性能好:B+ 树插入有序,页分裂少
- 存储小:4 字节(INT)或 8 字节(BIGINT)
- 可读性好:1, 2, 3 简单直观
- 调试方便:容易追踪数据
劣势
- 分布式困难:多库需要协调,避免 ID 冲突
- 安全性低:可遍历,容易被猜测
- 数据迁移麻烦:不同库的 ID 可能冲突
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100)
);
UUID
优势
- 全局唯一:无需协调,各节点独立生成
- 安全性高:不可遍历,难以猜测
- 数据迁移方便:不同库的数据可以合并
- 离线生成:不依赖数据库
劣势
- 性能差:无序插入导致页分裂
- 存储大:16 字节,比 BIGINT 大一倍
- 可读性差:难以记忆和调试
- 索引碎片化:随机插入导致索引碎片
CREATE TABLE users (
id CHAR(36) PRIMARY KEY,
name VARCHAR(100)
);
性能测试
插入性能(100 万条数据)
| 方案 |
耗时 |
索引大小 |
| 自增 ID |
12s |
30MB |
| UUID V4 |
25s |
65MB |
| 有序 UUID |
14s |
35MB |
查询性能(按 ID 查询)
| 方案 |
平均耗时 |
| 自增 ID |
0.1ms |
| UUID V4 |
0.3ms |
| 有序 UUID |
0.15ms |
选型建议
选择自增 ID 的场景
- 单库单表
- 内部系统(不需要隐藏 ID)
- 性能要求极高
- 数据量不大(< 1 亿)
选择 UUID 的场景
- 分布式系统
- 需要数据合并
- 安全性要求高(不暴露 ID)
- 需要离线生成 ID
混合方案
-- 内部使用自增 ID,外部暴露 UUID
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
uuid CHAR(36) UNIQUE NOT NULL,
name VARCHAR(100)
);
-- 查询时使用 UUID
SELECT * FROM users WHERE uuid = 'xxx';
-- 内部关联使用自增 ID
SELECT * FROM orders WHERE user_id = 123;
优化方案
1. 有序 UUID
// 使用时间戳前缀
public static UUID orderedUUID() {
long time = System.currentTimeMillis();
UUID uuid = UUID.randomUUID();
return new UUID(
(time << 22) | (uuid.getMostSignificantBits() & 0x3FFFFF),
uuid.getLeastSignificantBits()
);
}
2. Snowflake 算法
// 64 位有序 ID
long id = snowflake.nextId();
// 1635123456789012345
3. ULID
01ARZ3NDEKTSV4RRFFQ69G5FAV
总结
没有绝对的好坏,关键是根据场景选择:
- 单库、性能优先 → 自增 ID
- 分布式、安全优先 → UUID
- 需要有序 + 分布式 → Snowflake 或有序 UUID