Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > 线上MySQL自增ID用完

线上MySQL自增ID用完的原因、风险与完整解决方案

作者:我就是不信

MySQL自增ID用尽怎么办,本文详细解析自增ID上限的原因,提供监控告警、紧急扩列、分布式ID等解决方案,并给出建表规范,帮你从根源避免主键用尽问题,需要的朋友可以参考下

1. 问题背景:自增 ID 为什么会用尽

在 MySQL 中,AUTO_INCREMENT 是最常见的自增主键方案。它简单、有序、写入性能好,但有一个容易被忽视的硬性上限:自增列的数据类型决定了它能表示的最大值。

一旦自增计数器达到该类型的上限,后续插入就会直接报错:

ERROR 1062 (23000): Duplicate entry '2147483647' for key 'PRIMARY'

或者:

ERROR 1467 (HY000): Failed to read auto-increment value from storage engine

很多线上事故的根源,是建表时随手写了 int 主键,而业务量增长远超预期。一个 INT UNSIGNED 主键,理论上限是 42 亿左右,看似很多,但对于高频写入的日志表、消息表、订单流水表,几年甚至几个月就可能逼近上限。

2. 各数据类型的自增上限

先明确各整数类型的取值范围,这是判断风险的基础:

数据类型有符号最大值无符号最大值大致可支撑量级
TINYINT127255极小
SMALLINT32,76765,535小
MEDIUMINT8,388,60716,777,215中等
INT2,147,483,647(约 21 亿)4,294,967,295(约 42 亿)常见业务
BIGINT9,223,372,036,854,775,80718,446,744,073,709,551,615几乎用不完

结论很直接:新表主键优先使用 BIGINT UNSIGNED。 存储成本从 4 字节增加到 8 字节,但换来的是几乎不可能用尽的上限,这笔账非常划算。

3. 如何提前发现自增 ID 即将用尽

不要等报错才发现问题,日常巡检就应该监控自增使用率。

3.1 查看当前自增值与类型上限

SELECT
    TABLE_SCHEMA,
    TABLE_NAME,
    COLUMN_NAME,
    COLUMN_TYPE,
    AUTO_INCREMENT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'your_db'
  AND EXTRA LIKE '%auto_increment%';

结合 COLUMN_TYPE 判断上限,再计算使用率:

SELECT
    TABLE_NAME,
    AUTO_INCREMENT,
    CASE COLUMN_TYPE
        WHEN 'int' THEN 2147483647
        WHEN 'int unsigned' THEN 4294967295
        WHEN 'bigint' THEN 9223372036854775807
        WHEN 'bigint unsigned' THEN 18446744073709551615
    END AS max_value,
    ROUND(AUTO_INCREMENT / CASE COLUMN_TYPE
        WHEN 'int' THEN 2147483647
        WHEN 'int unsigned' THEN 4294967295
        WHEN 'bigint' THEN 9223372036854775807
        WHEN 'bigint unsigned' THEN 18446744073709551615
    END * 100, 4) AS used_percent
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 t.TABLE_SCHEMA = 'your_db'
  AND c.EXTRA LIKE '%auto_increment%'
ORDER BY used_percent DESC;

3.2 建立监控告警

把上面的查询接入监控系统,对 used_percent 设置分级告警:

注意 information_schema.TABLES 中的 AUTO_INCREMENT 在 MySQL 8.0 之后有缓存,可能不是实时精确值,但对趋势判断足够。

4. 已经用尽了怎么办:分场景处理

4.1 场景一:主键是 INT,尚未真正写满

这是最理想的情况,还有操作窗口。核心思路是把列类型从 INT 扩到 BIGINT。

ALTER TABLE t_order MODIFY COLUMN id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT;

关键注意点:

4.2 场景二:INT 已经写满,插入直接报错

此时无法再插入新数据,属于线上故障,需要快速止血。可选方案:

方案 A:紧急扩列(推荐)

如果表数据量可控、DDL 能在可接受时间内完成,直接执行上面的 MODIFY COLUMN。这是最干净的解法。

方案 B:临时改自增起始值(治标)

如果主键是有符号 INT 且当前只用到正数部分,理论上可以把自增步进到负数区间续命:

ALTER TABLE t_order AUTO_INCREMENT = 1;
SET SESSION sql_mode = '';

但这种方式极其危险:负数主键会污染业务逻辑、影响分页与排序、破坏下游依赖,仅作为最后手段,且必须评估所有消费方。更稳妥的做法是不要用负数,而是尽快扩列。

方案 C:切换到新的主键生成方案

如果扩列成本过高(例如表有几十亿行、DDL 需要数小时),可以考虑引入外部 ID 生成器,把新数据写入新表或新分片,老数据保留只读,逐步迁移。

4.3 场景三:BIGINT 也用尽了

虽然概率极低,但如果真的发生,说明单表已经承载了不合理的量级。此时应该做的是分库分表,而不是继续加大类型。常见做法:

5. 分布式 ID:从根上规避自增上限

当单表自增成为瓶颈,分布式 ID 是更长期的答案。主流方案对比如下:

方案有序性依赖优点缺点
数据库自增严格递增单库简单、无额外组件有上限、扩展性差
号段模式(Leaf-segment)趋势递增数据库性能高、易理解依赖 DB 分配号段
Snowflake趋势递增无本地生成、高性能依赖时钟,需处理回拨
UUID无序无完全去中心化无序导致索引分 裂、存储大
Redis INCR严格递增Redis简单高效依赖 Redis 可用性

对于大多数业务,号段模式是性价比最高的选择:数据库只负责分配号段(如一次分配 1000 个),应用在内存中递增使用,既避免了每次插入都访问 DB,又保证了趋势递增,对 B+ 树索引友好。

6. 预防胜于治疗:建表规范

把问题挡在发生之前,比事后救火成本低得多。建议在团队内固化以下规范:

7. 总结

自增 ID 用尽本质上是类型选型问题,而不是 MySQL 的缺陷。处理思路可以归纳为三层:

  1. 事前:主键一律用 BIGINT UNSIGNED,并建立使用率监控;
  2. 事中:发现使用率过高时,尽早通过在线 DDL 扩展列类型;
  3. 事后:若已写满,优先紧急扩列止血,同时评估分布式 ID 与分库分表的长期方案。

记住一句话:能用 BIGINT 解决的,就不要留给未来的自己。

以上就是线上MySQL自增ID用完的原因、风险与完整解决方案的详细内容,更多关于线上MySQL自增ID用完的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文