必须选bigint的场景是主键已接近或即将触碰int上限(2147483647),如高并发写入、分库分表全局id、日志表、自增接近峰值、年均增长超2000万行、使用snowflake或对接bigint外部系统;改型代价极高,涉及锁表、外键同步及全表copy。

如果主键短期内不会超过21亿,用INT更省空间、更快;一旦预估未来会超限(比如日增10万记录,撑不过6年),必须直接上BIGINT——别指望后期再改,代价远超预期。
什么时候必须选BIGINT?
不是“可能够用”,而是“已经或即将触碰上限”。INT有符号最大值是2147483647,按每天新增5万记录算,约116年才溢出;但若日增50万,4年就到头。高并发写入、分库分表后全局ID(如雪花ID)、日志类表(logs、events)基本没得选,直接BIGINT。
- 自增主键已接近
2147483647,或历史峰值超过1500000000 - 业务明确要支撑10年以上、年均增长超2000万行
- 使用分布式ID生成器(如Snowflake),输出天然是64位整数
- 和外部系统对接,对方主键字段已是
BIGINT,为避免类型转换失败
为什么不能等溢出再改?
ALTER TABLE ... MODIFY COLUMN在线改主键类型,在大表上会锁表、阻塞写入,且耗时随数据量非线性增长。一个5000万行的表,从INT升到BIGINT,在SSD+8核机器上也可能卡住10分钟以上。更麻烦的是外键依赖:所有引用该主键的子表字段,也得同步改,否则ADD FOREIGN KEY会报错ERROR 1005。
- 改主键前必须先
DROP PRIMARY KEY,再MODIFY COLUMN,最后ADD PRIMARY KEY,三步缺一不可 - 子表字段没同步改,插入时会报
Cannot add or update a child row: a foreign key constraint fails - MySQL 8.0+虽支持
ALGORITHM=INPLACE,但对主键字段类型变更仍不适用,本质还是copy table
INT和BIGINT对索引与JOIN的影响
主键即聚簇索引,字段越宽,B+树每页存的键越少,树高可能增加,范围查询和ORDER BY性能会轻微下降。更实际的影响在JOIN:两个BIGINT字段关联,比INT多消耗一倍内存带宽,尤其在临时表或哈希JOIN时。
- 单行记录中,
BIGINT比INT多占4字节,1亿行就是400MB额外存储 - 二级索引里存的是主键值,所以所有二级索引也会跟着变宽
- 如果只是做状态码、类型标识(如
order_status),哪怕叫id,也该用TINYINT,而不是盲目套用INT
别忽略UNSIGNED这个开关
INT UNSIGNED最大值是4294967295,比有符号多一倍容量,很多业务根本用不到负数主键。但要注意:一旦用了UNSIGNED,所有关联字段(包括外键列)都必须声明为UNSIGNED,否则建外键会失败,报错ERROR 1005: Can't create table。
-
INT UNSIGNED能撑到约11.6年(按日增100万算),比有符号翻倍 - ORM框架(如MyBatis、SQLAlchemy)默认不识别
UNSIGNED,可能把值读成负数,需显式配置类型映射 - 迁移时若原字段是
INT,加UNSIGNED属于类型变更,同样触发全表COPY
真正难的不是选哪个类型,而是预估业务增长节奏——多数人低估了数据膨胀速度,又高估了后期改造的可行性。宁可早半年定BIGINT,别拖到ERROR 1062: Duplicate entry '2147483647' for key 'PRIMARY'那天再救火。











