线上有张表,主键是 BIGINT AUTO_INCREMENT,跑了几年后,自增 ID 快用完了,怎么办?
别急着回答分库分表。先看主键是什么类型,再看当前 ID 增长到了哪里,最后才是怎么处理。 不同类型的自增主键,能用的空间差得非常大,处理方式也不一样。
这道题面试官真正想考的,不只是 MySQL 的数据类型,而是你有没有考虑过线上数据库的风险。
先看一个常见的建表语句:
CREATE TABLE order_record (
id BIGINT NOT NULL AUTO_INCREMENT,
order_no VARCHAR(40) NOT NULL,
user_id BIGINT NOT NULL,
amount DECIMAL(12, 2) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_order_no (order_no)
);
这里的 id 是有符号 BIGINT,取值范围是:
\[ -2^{63} \sim 2^{63}-1 \]
最大值约为 922 亿亿,也就是 9223372036854775807。
如果使用无符号 BIGINT,最大值还能扩大到 \(2^{64}-1\)。
所以,正常业务里,BIGINT 自增 ID 用尽并不常见。但如果建表时用的是 INT,情况就完全不同了。
有符号 INT 的最大值只有 2147483647,大约 21 亿。对于订单、流水、日志这些持续写入的表,积累到这个量级并非不可能。
第一步,我会先确认到底还剩多少空间。
SHOW CREATE TABLE order_record;
SELECT
TABLE_NAME,
AUTO_INCREMENT
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'order_record';
第一条确认主键定义,第二条查看当前自增值。
注意,这里的 AUTO_INCREMENT 是下一次分配时使用的自增值参考,不等于表中实际记录的最大 ID。删除过数据、手动插入过 ID,都可能让两者不一致。
如果表已经接近上限,就不能只看当前值,还得结合写入速度估算还能撑多久。线上最怕的不是今天用完,而是没有预警,某天插入突然开始报错。
如果主键是 INT,优先考虑扩容到 BIGINT。
例如原来的表结构是:
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
可以考虑改成:
ALTER TABLE order_record
MODIFY COLUMN id BIGINT NOT NULL AUTO_INCREMENT;
但这里有个坑:不要在高峰期直接执行这条 DDL,然后祈祷它赶紧结束。
具体的锁表行为、是否重建表、执行耗时,都跟 MySQL 版本、表结构和在线 DDL 能力有关。表里有几亿行数据时,一次结构变更可能带来明显的 I/O 压力,也可能影响业务写入。
生产环境通常要先评估变更成本,必要时使用经过验证的在线表结构变更方案,并提前确认主从复制延迟、磁盘空间和回滚预案。
还有一个容易漏掉的地方:Java 代码。
如果数据库字段已经改成 BIGINT,但实体类仍然使用 Integer,那数据库扩容也救不了应用。
public class OrderRecord {
private Long id;
private Long userId;
private BigDecimal amount;
}
主键对应 Long,不要继续使用 Integer。DAO、DTO、接口参数以及下游服务中涉及该 ID 的字段,也要一起检查。尤其是 ID 可能通过 JSON 传到 JavaScript 前端时,还要注意 JavaScript 安全整数范围的问题,不能简单认为数据库能存,所有客户端就都能精确处理。
如果原本已经是
BIGINT,而且真的快接近上限,那就不能指望简单修改字段类型解决问题了。
这时候可以考虑调整 ID 生成方案。
比如业务允许迁移到分布式 ID,可以使用雪花算法生成 long 类型的 ID。它不再依赖 MySQL 单表的自增计数器,不同节点可以生成不同的 ID。
不过,雪花算法不是换个工具类就结束了。机器时钟回拨、工作节点 ID 分配、服务重启后的节点冲突,这些都要处理好。否则数据库还没用尽,应用生成的 ID 就可能重复。
另一种方案是重新设计数据存储,比如按业务维度拆分数据,或者将历史数据归档,减少单表的持续增长压力。
但有一点要明确:分表不一定能解决主键耗尽。
如果所有分表仍然共用一个自增 ID 来源,或者拆表后依然使用相同的 ID 分配策略,问题可能只是换了个地方出现。
同样,删除历史数据也不代表自增 ID 就会自动回到较小的数值。特别是在生产环境里,不要为了腾出 ID 空间就随意重置自增计数器,更不能直接执行 TRUNCATE 这类有破坏性的操作。
最后说一个面试时经常被追问的问题:如果 ID 真用完了,线上会出现什么现象?
最直接的表现就是插入失败,可能看到类似这样的错误:
ERROR 1467 (HY000):
Failed to read auto-increment value from storage engine
具体错误信息会受到数据库版本、存储引擎和触发条件影响,但排查方向很明确:先确认字段类型和自增值,再看插入失败的 SQL、数据库错误日志,以及是否存在手动指定 ID、批量导入等特殊操作。
不要一看到插入失败,就认定是自增 ID 用尽。
如果确认已经触顶,先控制风险,避免业务继续大面积失败;再评估扩字段、切换 ID 生成器或者迁移数据的方案。涉及主键变更时,还要把外键、关联表、索引和上下游接口一起纳入改造范围。
这道题我的回答顺序很简单:先确认类型和剩余空间,INT 优先评估升级 BIGINT;BIGINT 接近上限,再考虑分布式 ID 或数据架构调整。
面试官问的是 ID 用尽怎么办,真正落到线上,关键还是别等到最后一个 ID 才开始想办法。