跑七年的订单表,主键号用完了:改一次要付全表代价

简介: 一张跑了七年的订单表,某天开始报主键冲突,查到最后是自增号用到了 2147483647。文章算清各整数类型的上限与余量、到顶之后的三种表现、改列类型的 COPY 代价,并给出不止一张表要跟着改的清单和自增水位监控。

大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。

去年帮一个团队看故障,订单表突然开始报主键冲突。开发说程序在重试,重试多了撞上重复键。我看了一眼表结构,id INT NOT NULL AUTO_INCREMENT,再看当前最大值,2147483647。这个数太有辨识度了,它就是 int 有符号的上限。那张表跑了七年,从零开始一格一格走到头,走到最后一天才开始报错。自增耗尽这件事最好玩的地方就在这:它在建表那天就注定了,只是账要七年后才结。

一、先算清上限

MySQL 的整数类型,上限是固定的。知道这张表一天写多少行,就能反推它还能跑多久。下面这张表可以直接对照,注意有符号和无符号差一倍。

类型 有符号上限 无符号上限 日均 100 万行能撑多久
TINYINT 127 255 十几秒
SMALLINT 32767 65535 不到一小时
MEDIUMINT 8388607 16777215 八天
INT 2147483647 4294967295 五年十一个月
BIGINT 9223372036854775807 18446744073709551615 用不完

INT 有符号是 21.47 亿。日均 100 万行的话,五年十一个月到顶。听着很久,可这套系统第七年才出事,说明前期量小,后面几年在加速。所以别用"平均日增"算,用最近三个月或者最近一个季度的日增算,那个数才是真的。

还有一个成倍消耗的坑:双主或者多写节点。两台都写,auto_increment_increment 设成 2,一台拿奇数一台拿偶数,21 亿的空间就只剩 10.7 亿。步长设置成 5,就只剩 4.3 亿。这个参数在建库时随手设的,做余量计算时最容易漏。

二、到顶之后会发生什么

不会突然全部停摆,表现分三种,一种比一种难查。

第一种是直接报错。插入时分配不到新号,MySQL 返回的是主键冲突,1062 Duplicate entry '2147483647' for key 'PRIMARY'。它每次都拿上限那个值去试,所以报出来的是重复键,不是"号用完了"。这个错还有人会误判成程序重试导致的重复提交,方向一偏,半天就过去了。

第二种是静默丢单。业务代码里有 try...catch,捕获异常记了一条日志,接口照样返回成功。用户以为下单成功,库里没有这行。这种最要命,等你发现的时候,缺口已经积累了几千单,还得靠对账一条条补。

第三种最隐蔽,是号段被复用。有人为了救火,把表清空、把 AUTO_INCREMENT 改回 1,想让号从头开始。表里还有数据的时候,这个 ALTER 根本不会生效,计数器只肯往上走;一旦表被清空,它就真的从 1 开始。空出来的号会被新数据占用,同一个 id 在缓存里、在日志里、在外部对账文件里指向两笔不同的业务,之后怎么查都对不上。号段一旦复用,历史数据的引用关系就断了,这件事比丢单更难收拾。

顺带说清版本差异。8.0 把自增计数器持久化进了 redo log,重启之后不会倒退,8.4 也一样。5.7 重启后会把计数器重置成 max(id)+1,如果最大值那几行被删了,重启就会复用已经用过的号。所以 5.7 的库更要注意,一次重启就可能埋一个雷。

三、余量怎么算

三条 SQL 就够,跑完把数记到巡检表里,每月看一次。

-- 1) 当前自增位与表的行数规模
SELECT TABLE_NAME, AUTO_INCREMENT, TABLE_ROWS, DATA_LENGTH
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
ORDER BY AUTO_INCREMENT DESC
LIMIT 20;

-- 2) 主键当前最大值(比 AUTO_INCREMENT 更准,尤其是刚删过数据)
SELECT MAX(id) AS max_id FROM t_order;

-- 3) 近 30 天的日增,用来算还能撑多少天
SELECT ROUND(COUNT(*) / 30) AS per_day
FROM t_order
WHERE created_at >= NOW() - INTERVAL 30 DAY;

余量 = 上限减 MAX(id)。可用天数 = 余量除以日增。落到行动上有三档:余量超过两年,记个水位告警就行;余量在半年以内,立项排期;已经到顶,先止血再迁移。

止血有个临时办法,把列改成无符号,上限直接翻一倍,ALTER TABLE t_order MODIFY id INT UNSIGNED NOT NULL AUTO_INCREMENT。代价还是重建表,只是重建完之后能多撑几年。它还有个前提:应用层得接得住这个值。Java 里 id 还是 int 的表,改完这里照样报错,只不过错从数据库挪到了应用。所以这招只适合"先把今晚过去"的场景,不能当长期方案。

-- 水位告警:按主键实际类型取上限,超过 70% 就报(BIGINT 不参与,比值永远接近 0)
SELECT c.TABLE_NAME,
       c.COLUMN_TYPE,
       t.AUTO_INCREMENT,
       ROUND(t.AUTO_INCREMENT / CASE
           WHEN c.DATA_TYPE = 'tinyint'   AND c.COLUMN_TYPE LIKE '%unsigned%' THEN 255
           WHEN c.DATA_TYPE = 'tinyint'   THEN 127
           WHEN c.DATA_TYPE = 'smallint'  AND c.COLUMN_TYPE LIKE '%unsigned%' THEN 65535
           WHEN c.DATA_TYPE = 'smallint'  THEN 32767
           WHEN c.DATA_TYPE = 'mediumint' AND c.COLUMN_TYPE LIKE '%unsigned%' THEN 16777215
           WHEN c.DATA_TYPE = 'mediumint' THEN 8388607
           WHEN c.DATA_TYPE = 'int'       AND c.COLUMN_TYPE LIKE '%unsigned%' THEN 4294967295
           WHEN c.DATA_TYPE = 'int'       THEN 2147483647
         END * 100, 1) AS used_pct
FROM information_schema.TABLES t
JOIN information_schema.COLUMNS c
  ON c.TABLE_SCHEMA = t.TABLE_SCHEMA
 AND c.TABLE_NAME = t.TABLE_NAME
WHERE t.TABLE_SCHEMA = DATABASE()
  AND c.EXTRA LIKE '%auto_increment%'
  AND c.DATA_TYPE IN ('tinyint', 'smallint', 'mediumint', 'int')
HAVING used_pct > 70
ORDER BY used_pct DESC;

这里有个口径要留意。8.0 里 information_schema.TABLES.AUTO_INCREMENT 是缓存值,默认 24 小时刷新一次。要拿准数,跑之前 ANALYZE TABLE,或者在会话里 SET information_schema_stats_expiry = 0。不然你看到的余量,可能是昨天的。

四、改类型的真实代价

MODIFY id BIGINT 这句话写起来一行,执行起来是另一回事。改列类型连 ALGORITHM=INPLACE 都不支持,只能走 ALGORITHM=COPY。COPY 的意思是建一张同结构的新表,把数据一行行拷过去,拷完再替换。这段时间里,这张表的写入全部阻塞,所有二级索引跟着重建,磁盘还要留出接近一张表的空间。亿级的表,几个小时起步。

方案 怎么做 代价 什么时候用
原生 ALTER ALGORITHM=COPY,建新表、逐行拷、拷完替换 写入全程阻塞,IO 翻倍,所有二级索引重建,主从延迟拉长,尾部要拿 MDL 能接受停写,或者有足够长的维护窗口
gh-ost / pt-osc 建影子表,拷数据,追增量,最后切换 需要额外一份空间,切换瞬间仍有短暂锁,工具本身要调 大表的主流做法
新表双写 建新表,代码双写,回填历史,切读 工程量最大,但可控,随时能退 核心交易表,不敢赌窗口

选哪条,看两件事:这张表能停多久,以及主从延迟能容忍多久。能接受停写两小时、从库追得上,原生 ALTER 最省事。一分钟都停不了,就走 gh-ost。已经在做架构改造,顺手一起做双写。

有个细节必须提前确认:这张表上有没有外键。MySQL 要求外键两侧的列类型完全一致,主表一改,约束会先把 DDL 拦下来,报外键不兼容。有外键的表,得先拆约束再改,改完重建。这个顺序搞反,DDL 会在第十分钟失败,然后回滚,白等一小时。

五、要跟着改的,不止这张表

我第一次帮人做这件事,只算了主库的重建时间,没算从库。第二天凌晨,从库回放这条 COPY DDL,SQL 线程卡在那里,延迟告警响了一路。后来整理了一份清单,每次动手前照着过一遍。

  • 从库不用单独改,但要盯它回放这条 DDL 要多久、磁盘够不够,SQL 线程卡住得有人管
  • 独立报表库、逻辑备份恢复出来的实例,不在同一条复制链路上,得单独改
  • 异构同步链路的目标端、分片中间件的路由规则与分片键范围配置
  • 有外键关联的子表,类型必须同步,否则约束先把 DDL 拦下来
  • 应用层的实体、DTO、接口出入参,int 到 long 是硬改
  • 缓存 key 的拼接方式、序列化格式,老数据里存的是 int
  • 导出脚本、对账脚本、数据分析的落库表结构

这里面最容易被忘的是缓存和序列化。id 从 21 亿变成 30 亿,如果某处用了 32 位有符号序列化,读出来就是负数。而这种错不会在改表当天暴露,要等新号真正越界才出现,可能是一个月后。

六、预防:建表那一刻就定下来

新表一律 BIGINT UNSIGNED,没有例外。一行多四个字节,一亿行大约四百兆;每个二级索引的叶子还要再存一份主键,这个数得往上加。跟一场亿级重建比,这笔账不用算。

自增不要拿来做业务单号。它不连续,回滚和批量插入都会跳号;它可预测,看号的大小就能反推业务量;它还绑在单库上,多写节点只能靠步长硬凑。业务单号要的是唯一、可读、可追溯,自增只占一个单表内的唯一。

水位监控要提前建。上面那段 SQL 塞进巡检脚本,每月跑一次,把用量超过 70% 的表列出来。这条成本最低,收益最直接,我现在的库里固定有这么一项。

七、避坑清单

AUTO_INCREMENT 的当前值不能只看 information_schema,它是缓存值,删过数据的表要用 MAX(id) 复核。两个数差得远,说明有大量空洞,真实余量比看上去少。

双主、多写节点的库,余量要按步长折算。auto_increment_increment=2 就是打对折,这个数很容易被漏掉,建库时设的,几年后没人记得。

最后一条是我自己踩的。有次在测试环境复现号段耗尽,我把表清空,又把 AUTO_INCREMENT 改回 1,重新灌了一批数据。当时对上了,觉得验证成功。上线前我照同样的做法在生产演练了一次,两千多条订单的 id 和历史归档数据撞上了,对账直接差钱。从那以后我明白,自增号段是"一次性资源",删掉的行不会把号还给你。演练也一样,宁可用新表造,也不要动生产表的计数器。

写在最后

自增耗尽不是容量问题,是类型选错了一次、几年后来收账。它的特点是可预测:上限固定,日增可测,余量能算。所以它属于少数"完全能提前发现"的故障。

这篇不讨论主键该选自增还是 UUID,那是建表时的选择。这里只讲已经选了 int、并且已经到顶之后,这笔账怎么结。号段能扩,类型不能,这是两件事。

真到了要改的那天,别急着写 ALTER。先数清楚有多少张表、多少个子表、多少处代码在用它。改表是半天的事,改全链路是两周的事。

那张表最后没走原生 ALTER,换了在线工具重建,切过去之后 id 接着往下走,业务没停。

你们库里最老的那张表,主键是什么类型?算过还能撑几年吗?把这个数发到评论区,我帮你核一遍预算。

我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋

相关文章
|
10天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)
3861 17
|
10天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
4075 48
|
11天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
1915 6
|
5天前
|
云安全 人工智能 安全
|
5天前
|
存储 弹性计算 人工智能
2026 年阿里云双十一活动入口攻略,新客服务器优惠通道、三大会场入口梳理
综合来看,2026年阿里云双十一活动入口清晰: 阿里云轻量应用服务器、阿里云ECS云服务器、阿里云GPU云服务器、阿里云百炼大模型服务平台 活动会场、综合权益中心、99计划专场三大入口覆盖服务器、AI权益与续费特价;新用户通过控制台首页banner、权益中心、产品页三处路径进入专属专场,老用户通过控制台弹窗和费用中心权益中心进入老客专区,企业用户做企业实名后解锁专属满减与阶梯折扣。
817 0
|
5天前
|
Linux 开发工具 iOS开发
2026最新codex下载安装保姆级使用教程(下载+安装+登录+使用,含所有历史版本)
本文详解OpenAI Codex国内快速上手指南:提供Win/macOS/Linux全平台免安装包、npm/Homebrew等多方式安装、账号/API Key双登录、简体中文切换,覆盖下载、安装、汉化、使用及高频避坑(如微软商店失败、npm超时等),10分钟即可跑通。
997 2
|
11天前
|
安全 算法 定位技术
《高性价比人生指南》完整原版:普通人用最少的资源,过好这一生的底层逻辑~How To Live Better
📖《高性价比人生指南》是一份基于实证、拒绝焦虑的生存手册,倡导以“人生时薪”为视角,重新计算时间、金钱与精力的成本与收益。它不承诺逆袭,只教人算清账:省下的钱若耗掉时间便亏;买体验比买物品更值;睡眠是最高杠杆的健康投资;关系贵在情绪价值而非表面热络;学习重在改变行为而非自我安慰。核心理念:用低成本、高回报的微小行动,换取长期的选择权——不是成为谁,而是掌控自己人生的主动权。
2137 6
|
云安全 安全 网络安全
|
24天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
4251 57