主键耗尽需区分“真耗尽”与“虚高”:查max(id)和information_schema中auto_increment值,若均近上限则为真耗尽,应升级bigint;若仅auto_increment虚高,可用alter table调整续命。

主键用完不是“还能撑几天”的问题,而是写入已实质中断——只要 AUTO_INCREMENT 卡在上限(比如 4294967295),后续所有 INSERT 都会报 ERROR 1062、ERROR 1467 或 ERROR 1690,业务写入立即失败。真正要做的,是分清“真耗尽”和“虚高”,再选对方案,而不是一上来就改类型或换 UUID。
先确认是不是真耗尽
别只看 DESCRIBE 或 SHOW CREATE TABLE,它们不反映运行时真实值。必须查两个数:
-
当前最大已用 ID:
SELECT MAX(id) FROM your_table; -
MySQL 下次准备分配的 ID:
SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'your_table';
如果两者都接近上限(如都是 4294967295),才是真耗尽;如果 AUTO_INCREMENT 远大于 MAX(id)(比如删了大量数据但没优化),属于“虚高”,可临时调整续命。
虚高场景:秒级修复,不锁表
适用于 MAX(id) 还没到上限(如 4294967290),但 AUTO_INCREMENT 被推高到 4294967295 的情况(常见于长事务、批量插入后回滚)。
- 执行:
ALTER TABLE your_table AUTO_INCREMENT = 4294967296; - 注意:设的值必须 > 当前
MAX(id),否则 MySQL 启动时自动修正为MAX(id)+1 - 该操作不重建表,秒级完成,但只是治标;需配合归档冷数据 +
OPTIMIZE TABLE回收空间并稳定自增值
真耗尽场景:升级 BIGINT 是首选
INT UNSIGNED 最大值 4294967295 已被占满,必须扩宽。BIGINT UNSIGNED 最大值约 18446744073709551615,按每秒 1 万插入算,够用 5000 年。
- 基础语句:
ALTER TABLE your_table MODIFY id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY; - 锁表不可避免:MySQL 5.7/8.0 对整数类型扩宽仍触发全表拷贝,超 50GB 表建议用
pt-online-schema-change - 外键必须同步改:所有引用该字段的子表也要
MODIFY对应列,否则 ALTER 直接报 ERROR 1832 - 应用层变量要检查:Java 的
Integer、Go 的int、MyBatis 的resultType、JSON 序列化逻辑,都可能因位宽不匹配截断或溢出
新表设计:一开始就用 BIGINT
别等爆了再改。INT 虽省 4 字节,但代价远高于收益:
- 单表超 500 万行就该考虑分表,而 500 万离 42 亿差太远,说明你更早会遇到性能瓶颈,而非主键耗尽
- BIGINT 多占 4 字节/行,对聚簇索引影响有限;真正吃空间的是二级索引——但相比换 UUID(36 字节主键让所有二级索引暴增),它更轻量、更有序、更适合 B+ 树
- 分布式 ID(如 Snowflake)适合多节点写入场景,但引入额外服务依赖;单机或小集群下,BIGINT 更简单可靠











