自增主键用尽了怎么办?INT溢出、在线迁移与预防策略全解析

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

前两天在技术群里看到一条消息:"生产环境订单表插入报错了,Duplicate entry '2147483647' for key 'PRIMARY'。"

这是典型的INT自增主键溢出

在展开之前,先把几个关键概念说清楚。

什么是自增主键? 主键是表中唯一标识每一行记录的字段。自增主键(AUTO_INCREMENT)是MySQL的一种机制------每次插入新记录时,数据库自动为这个字段分配一个递增的整数值,不需要应用层手动指定。好处是简单、高效、天然保证唯一性。

什么是主键溢出? 每种数据类型都有取值上限。INT类型用4个字节存储,有符号时最大值为2,147,483,647(约21亿),无符号时最大值为4,294,967,295(约42亿)。当自增主键的值达到这个上限后,InnoDB内部的计数器不会自动循环,后续INSERT操作会持续触发ER_DUP_ENTRY错误,导致整库写入停服

为什么这个问题越来越常见? 很多团队在项目初期用INT做主键,认为"21亿够用了"。但对于日均写入百万行的订单表、日志表、流水表,21亿在几年内就可能触达。更关键的是,很多团队没有监控自增ID消耗进度的习惯,等到报错了才发现------而这时候留给迁移准备的时间窗口可能只有数周。

一、怎么提前发现?

在问题爆发之前,可以主动监控ID消耗进度。通过查询information_schema,可以精准获取各表的AUTO_INCREMENT值和类型上限的比例:

bash 复制代码
SELECT 
    t.TABLE_SCHEMA,
    t.TABLE_NAME,
    c.DATA_TYPE,
    t.AUTO_INCREMENT,
    CASE 
        WHEN c.DATA_TYPE = 'int' AND c.COLUMN_TYPE NOT LIKE '%unsigned%' THEN 2147483647
        WHEN c.DATA_TYPE = 'int' AND c.COLUMN_TYPE LIKE '%unsigned%' THEN 4294967295
        WHEN c.DATA_TYPE = 'bigint' THEN 9223372036854775807
    END AS MAX_VALUE,
    (t.AUTO_INCREMENT / CASE 
        WHEN c.DATA_TYPE = 'int' AND c.COLUMN_TYPE NOT LIKE '%unsigned%' THEN 2147483647
        WHEN c.DATA_TYPE = 'int' AND c.COLUMN_TYPE LIKE '%unsigned%' THEN 4294967295
        ELSE 9223372036854775807
    END) * 100 AS usage_ratio
FROM information_schema.TABLES t
JOIN information_schema.COLUMNS c 
    ON t.TABLE_SCHEMA = c.TABLE_SCHEMA 
    AND t.TABLE_NAME = c.TABLE_NAME
WHERE c.EXTRA = 'auto_increment'
    AND t.AUTO_INCREMENT IS NOT NULL
HAVING usage_ratio > 80;

监控阈值建议:80%触发预警,90%触发紧急告警。在海量写入场景下,从90%到100%的窗口期可能只有数周,留给迁移准备的时间非常有限。

二、在线迁移方案:INT → BIGINT

最直接的方案是把自增主键从INT升级为BIGINT。BIGINT占用8字节,有符号上限约922亿亿,足够绝大多数业务使用。

不能直接执行ALTER TABLE 。修改数值类型会触发MySQL重建整张表,锁表时间与数据量正相关。几百GB的表执行ALTER TABLE t MODIFY id BIGINT,可能持续数小时,期间写入完全阻塞。即使MySQL 5.6+支持ALGORITHM=INPLACE,修改数值类型也不支持原地升级。

生产环境需要用gh-ost做无锁迁移。核心流程是:创建影子表 → 复制存量数据 → 通过Binlog增量同步 → 原子切换。

有几个容易踩的坑需要注意:

坑1:AUTO_INCREMENT值不会自动继承。 gh-ost新建的影子表初始AUTO_INCREMENT=1。如果不处理,切流后新插入数据会从1开始,必然冲突。正确做法是在迁移完成后立即同步:

bash 复制代码
ALTER TABLE _t_gho AUTO_INCREMENT = (
    SELECT AUTO_INCREMENT FROM information_schema.TABLES 
    WHERE TABLE_SCHEMA='db' AND TABLE_NAME='t'
);

坑2:外键关联的子表字段必须同步改为BIGINT。 忽略这一步会导致ERROR 1215

坑3:应用层代码需要同步升级。 Java应用中int类型的ID字段必须升级为long,ORM映射也需要调整。

三、更彻底的方案:分布式ID

如果团队已经在做分库分表,或者业务量增长极快,INT迁移到BIGINT可能只是"续命几年"。更彻底的方案是逐步废弃自增主键,改用分布式ID生成方案

常见方案包括:雪花算法(Snowflake)、号段模式(如美团Leaf)、以及基于数据库的全局序列。

金仓KES Sharding内置了全局序列能力,提供高性能、无冲突的全局唯一ID生成机制。对于从集中式向分布式演进的银行核心系统来说,全局序列是分布式架构的基础组件之一------应用层不需要自己维护ID生成逻辑,数据库层面统一提供。

四、预防比迁移更重要

如果你的系统还没到21亿,现在就是最好的预防时机。

新建表时直接用BIGINT。 BIGINT只比INT多占4字节存储空间,但对于避免未来的迁移成本来说,这点开销可以忽略不计。

对于已上线的INT表,把上面的监控脚本加到日常巡检中。接近80%时提前规划迁移方案,不要等到报错了才行动。

五、小结

自增主键溢出是一个"看起来很远、实际很近"的问题。INT的21亿上限,对于日均百万写入的系统,可能就是几年的光景。提前监控、提前规划在线迁移方案、新建表直接用BIGINT------这三件事做好,就能避免被一个主键类型卡住整条业务线。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~

相关推荐
迷茫的大专生2 小时前
高可用总结
redis·mysql·nginx·高可用
老纪的技术唠嗑局2 小时前
Agent 习惯性删库跑路,数据库纷纷学 Git 续命
数据库·人工智能
Data_Journal2 小时前
使用 AutoScraper 进行网页抓取:分步教程
大数据·开发语言·数据库·python·scrapy
白远山3 小时前
健身场馆无人自动化解决方案:从架构到落地实践
java·开发语言·数据库·数据挖掘·需求分析
虎虎(_ _)。゜zzZ3 小时前
SQLAlchemy入门教程
数据库·后端·python·sql·ai·sqlalchemy
迷茫的大专生3 小时前
服务器部署与 MySQL 运维学习笔记:从 Ansible 到高可用与性能优化
mysql·nginx·ansible·mysql优化·keepalive
Data_Journal4 小时前
如何将网页抓取用于机器学习
大数据·开发语言·数据库·python·scrapy
Lyra_Infra4 小时前
从 MySQL 到达梦:一次信创隔离环境里的数据库迁移踩坑实录
数据库·后端·mysql
数据工匠老o4 小时前
sysbench/TPC-C/自定义脚本:数据库压测工具对比与实战流程
数据库·测试