前言
建表第一件事就是设计主键ID,很多新手直接无脑写
id INT PRIMARY KEY AUTO_INCREMENT,等到业务数据量上来,INT溢出、分库分表主键冲突、性能瓶颈等问题集中爆发。主键ID选型不只是选字段类型,还要考虑:数据上限、存储空间、写入性能、分库分表、索引体积、业务是否允许自增。本文对比常用ID类型,讲清楚适用场景和线上踩坑点。
一、先看各数字类型存储范围与占用空间
只列出主键最常用的整数类型(无符号 unsigned)
| 类型 | 占用字节 | 无符号取值范围 | 最大行数参考 |
| ---- | ---- | ---- | ---- |
| TINYINT | 1 | 0 ~ 255 | 255条,几乎不用做主键 |
| SMALLINT | 2 | 0 ~ 65535 | 6万+,小型配置表可用 |
| MEDIUMINT | 3 | 0 ~ 16777215 | 约1600万 |
| INT | 4 | 0 ~ 4294967295 | 约42亿 |
| BIGINT | 8 | 0 ~ 18446744073709551615 | 接近1.8e19,几乎不会溢出 |
重点提醒:不加UNSIGNED,最大值直接减半 。INT 有符号范围:-2147483648 ~ 2147483647,上限仅21亿。很多项目踩坑,业务增长到2000万~2亿时,ID就逼近上限。
二、方案1:自增INT(INT UNSIGNED AUTO_INCREMENT)
sql
CREATE TABLE demo (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
✅ 优点
- 占用4字节,相比BIGINT节省一半存储空间;
- 顺序自增,主键索引有序,InnoDB聚簇索引写入性能好;
- 简单,数据库自动维护,开发成本低;
- 索引体积更小,内存可以缓存更多索引页。
❌ 缺点
- 上限42亿,业务持续增长有溢出风险;
- 分库分表场景,多库自增ID会重复;
- ID连续,容易被爬虫遍历(比如接口
?id=1001遍历所有数据); - 数据删除后ID不会回收,只是空号,不能复用。
✅ 适合场景:
中小型业务,预估总数据远小于亿级、不分库分表、单库单表。
例如后台配置表、内部管理系统。
❌ 不适合:
高增长业务、用户表、订单表,未来可能分库分表的业务。
三、方案2:自增BIGINT(BIGINT UNSIGNED AUTO_INCREMENT)
sql
CREATE TABLE demo (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
✅ 优点
- 上限极大,几乎不用担心溢出;
- 顺序写入,InnoDB聚簇索引友好;
- 单库单表开发简单。
❌ 缺点
- 8字节,比INT多一倍存储;表越大,主键、二级索引体积都会变大(二级索引会保存主键值);
- 同样存在分库分表ID冲突、ID可被遍历的问题。
✅ 适合场景:
绝大多数互联网业务,用户、订单、流水表;单库未来有可能分库分表,但是暂时不分。
【推荐默认选型】新项目如果拿不准,直接选 BIGINT UNSIGNED。存储空间成本在现代服务器上基本可以接受,避免后期INT溢出改表的灾难。
⚠️ 线上重大事故场景:业务前期用INT,数据增长到20亿,ID即将溢出,大表修改字段类型是重量级DDL,锁表、耗时极长,风险极高。
四、方案3:字符串类型ID(UUID / UUIDv4 /雪花ID存CHAR/VARCHAR)
sql
-- 示例,字符串主键
id CHAR(36) PRIMARY KEY
✅ 优点
- 全局唯一,多库分库分表不会冲突;
- ID无规律,不能简单通过递增遍历数据。
❌ 缺点
- 占用空间大(36字节),索引膨胀严重;
- UUIDv4是完全随机,无序写入。InnoDB聚簇索引要求主键有序,随机ID会造成大量页分裂、碎片,写入性能大幅下降;
- 更长的主键,所有二级索引都会变大,缓冲池利用率变差;
- 字符串比较比整数慢,查询、关联JOIN性能弱于数字主键。
优化方案:UUIDv1 / UUIDv7(有序UUID),可以缓解页分裂问题。
✅ 适合场景:
需要跨数据库全局唯一,对写入性能要求不极致的场景。
❌ 不适合:高并发写入的核心业务表(订单、流水)。
五、方案4:雪花算法ID,存储在BIGINT
雪花ID本质是一个64bit数字,可以直接放入BIGINT。
✅ 优点
- 64位数字,全局唯一,支持分布式、分库分表;
- 包含时间戳,ID自带时间信息,可以粗略排序;
- 存储占用8字节,和自增BIGINT一样,索引体积小;
- 不会暴露业务数据总量。
❌ 缺点
- 依赖外部服务或者程序生成ID,数据库不能自动生成;
- 需要保证机器ID不重复;时钟回拨会产生重复ID,需要业务层处理;
- 不是严格连续自增。
✅ 适合场景:
分布式系统、分库分表、高并发核心业务(订单、用户)。
六、不推荐的ID方案
- VARCHAR自增ID:完全没必要,字符串索引性能差;
- 复合主键:尽量避免做主表主键,二级索引开销巨大;
- 使用业务字段做主键(手机号、身份证号):业务字段会变更,主键一旦修改,所有二级索引都要同步更新,灾难级开销。
原则:主键尽量无业务含义,只作为唯一标识。
七、InnoDB主键核心底层原理(为什么顺序很重要)
InnoDB是聚簇索引,整张表的数据存放在主键B+树叶子节点。
- 有序主键(自增ID):新数据持续追加到B+树末尾,很少发生页分裂,写入快,碎片少。
- 无序主键(随机UUID):新ID随机插入B+树中间,频繁页分裂,IO暴涨,性能下降。
这也是为什么不推荐直接使用UUIDv4作为主键最核心的原因。
八、选型决策速查表
| 场景 | 推荐类型 |
|---|---|
| 小型内部系统,数据预估千万以内,单库 | INT UNSIGNED AUTO_INCREMENT |
| 通用新项目,互联网业务,单库,不确定未来数据量 | BIGINT UNSIGNED AUTO_INCREMENT |
| 分布式/分库分表,高并发 | BIGINT 存储雪花ID |
| 跨系统数据同步,不需要超高写入性能 | CHAR(36) 有序UUID(UUIDv7) |
九、线上踩坑清单
- INT不加UNSIGNED,上限直接减半,更容易溢出;
- 前期图省钱选INT,后期数据量暴涨,大表ALTER修改字段类型,锁表引发故障;
- 直接用UUIDv4做主键,高并发下数据库写入卡顿;
- 雪花ID存入VARCHAR,白白浪费空间,丧失数字类型性能优势;
- 主键设置有业务含义,业务变更时要修改主键;
- 主键太长,二级索引体积膨胀,导致内存缓存命中率下降,查询变慢。
十、最佳实践总结
- 主键尽量选用无业务含义、固定长度、有序的字段;
- 单库新项目,拿不准就直接 BIGINT UNSIGNED AUTO_INCREMENT,牺牲少量磁盘空间换取安全,规避溢出风险;
- 分布式、分库分表场景,使用雪花ID,存入BIGINT;
- 尽量避免直接使用原生UUIDv4作为主键;
- 不要用手机号、邮箱等业务唯一字段代替主键,可以单独建唯一索引。
一句话总结:小表可以INT;绝大多数业务优先BIGINT自增;分布式场景BIGINT存雪花ID;谨慎使用字符串UUID主键。