MySQL 表的主键应该如何设计?
简化版
MySQL InnoDB 表主键建议短小、稳定、唯一、尽量递增;主键不仅用于唯一标识记录,还决定聚簇索引的数据组织方式,并会被所有二级索引引用。
详细版
InnoDB 使用聚簇索引组织表数据,通常主键索引的叶子节点保存整行数据。因此主键设计会影响插入性能、范围查询、二级索引大小和缓存效率。
常见推荐是使用 BIGINT 自增主键或趋势递增的全局 id。它短、比较快、插入位置集中,二级索引中引用成本也较低。随机 UUID 虽然全局唯一,但较长且插入随机,可能导致页分裂、索引膨胀和缓存命中下降。
当然,主键选择也要结合业务。业务字段如果会变化、较长、敏感或不稳定,不适合作为主键;可以通过唯一索引保证业务唯一性,用代理主键负责存储组织。
完整版教学
一、主键不只是唯一约束
在很多数据库知识里,主键常被理解为“唯一且非空”。在 InnoDB 中,它还承担聚簇索引键的角色。
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
order_no VARCHAR(64) UNIQUE,
user_id BIGINT,
amount DECIMAL(10,2)
);
这里 id 不只保证唯一,它还决定整行数据在聚簇索引 B+ 树中的组织方式。
记忆钩子:InnoDB 主键既是身份证,也是数据摆放路线。
二、为什么要短小
InnoDB 二级索引叶子节点保存的是主键值。主键越大,每个二级索引项越大。
假设表有 1000 万行、6 个二级索引:
| 主键类型 | 长度 | 对二级索引影响 |
|---|---|---|
BIGINT | 8 字节 | 成本较低 |
CHAR(36) UUID | 36 字节 | 每个二级索引都膨胀 |
VARCHAR(128) | 可变且更长 | 比较和存储成本更高 |
这不是只多几十字节的小事。乘以千万行和多个索引后,空间、缓存命中率、IO 都会受影响。
因此主键字段应尽量短小,常见选择是 BIGINT。
三、为什么要稳定
主键一旦被其他表引用或被二级索引保存,修改成本很高。业务字段如果可能变化,不适合作主键。
例如手机号:
-- 不推荐把手机号作为主键
phone VARCHAR(20) PRIMARY KEY
用户换手机号时,主键要变,关联表也要跟着变,还可能暴露隐私。更稳的设计是:
id BIGINT PRIMARY KEY,
phone VARCHAR(20),
UNIQUE KEY uk_phone(phone)
id 负责稳定标识和存储组织,phone 通过唯一索引保证业务唯一。
四、为什么尽量递增
递增主键插入时通常集中在 B+ 树右侧,页分裂较少;随机主键插入位置分散,可能在各个页中间插入,导致页分裂和数据页变动。
示意:
递增 id:1,2,3,4,5 -> 主要向右追加
随机 UUID:A7, 19, F3, 02 -> 到处插入
如果每秒插入 5000 行订单,递增主键能让写入路径更稳定。随机 UUID 的优势是分布式生成简单、难猜,但在 InnoDB 聚簇索引中会带来额外成本。
折中方案可以用趋势递增的分布式 id,如雪花算法 id。
五、代理主键和自然主键怎么选
自然主键来自业务字段,如身份证号、订单号、邮箱。代理主键是业务无关字段,如自增 id。
| 类型 | 优点 | 风险 |
|---|---|---|
| 自然主键 | 业务含义明确 | 可能变更、较长、敏感 |
| 代理主键 | 稳定短小 | 需要额外唯一约束保证业务唯一 |
大多数互联网业务更偏向代理主键 + 业务唯一索引。这样既保证存储层稳定,又保证业务层不重复。
例如订单表:
id BIGINT PRIMARY KEY,
order_no VARCHAR(64) NOT NULL,
UNIQUE KEY uk_order_no(order_no)
六、分布式场景要考虑生成方式
单机 MySQL 可以用自增主键,但分库分表或多写节点下,自增 id 可能冲突或难以全局有序。
常见方案:
数据库自增:简单,单库友好
号段模式:批量取号,减少数据库压力
雪花算法:趋势递增,全局生成
UUID:全局唯一,但随机且较长
如果未来要分库分表,可以提前选择全局唯一且趋势递增的 id。不要只看当前写表方便,也要看后续扩展和索引成本。
七、常见误区与追问
- 误区:主键只要唯一就行。 InnoDB 主键还决定聚簇索引组织方式,并影响二级索引。
- 误区:业务字段有唯一性就适合做主键。 如果字段会变化、较长或敏感,就不适合作主键。
- 误区:UUID 做主键没有代价。 UUID 较长且随机,可能增加页分裂、索引体积和缓存压力。
- 误区:没有主键也没关系。 InnoDB 会生成隐藏 row id,但不利于业务查询和维护。
- 追问:为什么主键会影响二级索引? 二级索引叶子节点保存主键值,主键越大二级索引越大。
- 追问:订单号能不能做主键? 如果订单号较长或生成规则可能变化,更建议代理主键加订单号唯一索引。
八、加强记忆
主键设计记住“四字诀”:短、稳、唯一、递增。短是为了索引空间,稳是为了关联和维护,唯一是基本要求,递增是为了 InnoDB 写入和页组织。业务唯一交给唯一索引,聚簇组织交给代理主键。