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