数据库主键应该如何设计?自增 ID 和 UUID 怎么选?
简化版
主键应该稳定、唯一、尽量短,并且不包含会变化的业务含义。自增 ID 对 B+ 树和范围查询友好,但分布式生成和暴露业务规模要处理;UUID 全局生成方便,但较长且随机,可能增加索引空间和写入离散度。
详细版
主键设计原则:
- 稳定:不要使用会变的手机号、邮箱、身份证状态字段做主键;
- 唯一:必须能稳定标识一行;
- 短小:InnoDB 二级索引会存主键值,主键越长,索引越大;
- 无业务含义或弱业务含义:避免业务规则变化导致主键体系崩;
- 适合写入模式:递增或趋势递增通常更适合聚簇索引。
常见选择:
| 类型 | 优点 | 缺点 |
|---|---|---|
| 自增 ID | 简单、短、索引友好 | 分库分表下要解决全局唯一,可能暴露规模 |
| UUID | 客户端可生成、全局唯一概率高 | 长、随机、索引和存储成本高 |
| 雪花 ID | 趋势递增、分布式友好 | 依赖时钟和机器号管理 |
完整版教学
一、主键不是随便找个唯一字段
主键是整张表的身份标识。它会被外键引用、被业务传递、被索引使用,也会影响数据物理组织方式。
一个好主键要长期稳定。如果用手机号做主键,用户换手机号怎么办?如果用邮箱做主键,邮箱改绑怎么办?业务字段会变化,就不适合做主键。
二、InnoDB 下主键还影响聚簇索引
InnoDB 表的数据按聚簇索引组织,通常就是主键索引。主键叶子节点存整行数据,二级索引叶子节点存二级索引列和主键值。
这意味着主键越长,所有二级索引都会被放大。比如用 36 字符 UUID 做主键,二级索引里也要保存这个长主键,缓存效率和存储成本都会受影响。
三、自增 ID 的优点和边界
自增 ID 短、递增、索引友好。插入时大多追加到 B+ 树右侧,页分裂和随机写压力相对小。
但自增 ID 也有问题:
- 分库分表后各库自增会冲突;
- 对外暴露可能泄露业务规模;
- 多主写入或跨系统合并数据时不方便;
- 高并发极端场景可能有自增锁或热点页压力。
这些问题通常通过号段、雪花 ID、隐藏内部 ID、对外使用短码等方式解决。
四、UUID 的优点和问题
UUID 最大优点是生成方便,不依赖数据库,跨系统合并也比较自然。
问题是它通常较长,而且随机分布。作为聚簇主键时,插入位置分散,可能增加页分裂和缓存压力。作为二级索引引用值时,也会放大索引体积。
如果必须使用 UUID,可以考虑:
- 使用二进制存储而不是字符串;
- 使用有序 UUID 或 ULID 等趋势有序方案;
- 内部仍用自增或雪花 ID,对外暴露 UUID。
五、雪花 ID 为什么常见
雪花 ID 通常由时间戳、机器号、序列号组成。它的特点是分布式生成、趋势递增、长度相对可控,适合订单、消息、日志等高并发系统。
它的风险在于时钟回拨和机器号冲突。如果系统时钟倒退,可能生成重复或乱序 ID;如果机器号管理不严,也可能冲突。所以要有时钟保护和节点管理策略。
六、业务主键和数据库主键可以分开
有些业务字段也要求唯一,比如订单号、用户账号、手机号。这些可以建唯一索引,但不一定要作为数据库主键。
常见做法是:
id: 内部主键,短小稳定
order_no: 业务单号,唯一索引,对外展示
这样既保证数据库结构稳定,又满足业务查询和展示。
还有一个常见工程做法是“内部 ID 和外部 ID 分离”:内部 id 用短小趋势递增的 bigint,方便聚簇索引和关联;外部 public_id 或 order_no 用不暴露规模的编码,并建唯一索引。这样既保护内部存储效率,也满足对外安全和业务识别。
七、常见误区与追问
| 主键方案 | 适合场景 | 主要风险 |
|---|---|---|
| 自增 ID | 单库、写入顺序性要求高 | 分布式唯一和规模暴露 |
| UUID 字符串 | 多端生成、跨系统合并 | 长、随机、索引空间大 |
| 雪花 ID | 分布式高并发业务单号 | 时钟回拨、机器号冲突 |
易错点:业务唯一字段可以建唯一索引,但不一定要当主键。主键优先服务“稳定标识一行”和“索引组织”,业务编码优先服务“对外识别和查询”。
主键长度对索引成本很敏感。假设一张表有 5 个二级索引、1000 万行,若主键从 8 字节 bigint 换成 36 字节 UUID 字符串,二级索引叶子节点里每行多约 28 字节主键引用,粗略就是:
28B * 10,000,000 * 5 ≈ 1.4GB
这还没算页目录、填充率和字符集开销,所以 InnoDB 表里长主键会实实在在放大二级索引和 Buffer Pool 压力。
- 误区:只要字段唯一就适合做主键。 手机号、邮箱、身份证绑定状态都可能变化,更适合唯一索引,不适合做稳定主键。
- 误区:UUID 永远比自增 ID 更安全。 UUID 不暴露规模,但作为聚簇主键可能带来更大的存储和随机写成本。
- 误区:自增 ID 在分布式系统完全不能用。 可以通过号段、分库步长、发号服务等方案使用,只是要明确全局唯一策略。
- 追问:为什么 InnoDB 主键要尽量短? 二级索引叶子节点会保存主键值,主键越长,所有二级索引都被放大。
- 追问:雪花 ID 为什么是趋势递增而不是严格全局递增? 不同机器并发生成时整体大致按时间增长,但跨节点的精确顺序不应作为强业务语义依赖。
- 追问:对外不想暴露自增 ID 怎么办? 内部用短主键,对外用订单号、短码、UUID 或加密 ID,并对外部字段建唯一索引。
八、加强记忆
主键要稳定、短小、唯一、少业务含义。自增 ID 简单且索引友好,UUID 生成方便但长且随机,雪花 ID 适合分布式但要处理时钟和机器号。业务唯一字段可以建唯一索引,不一定要当主键。