上周看到一个紧急求助:他们电商平台的订单表突然无法插入数据,报错 “Duplicate entry for key ‘PRIMARY’”,排查后发现 —— 自增 ID 用完了。原来初期图省事用了INT类型,日均 100 万订单的量,不到 2 年就触达了 21 亿的上限,直接导致业务中断 4 小时。

这不是个例。自增 ID 作为最常用的主键方案,看似稳定,实则藏着 “耗尽” 的隐形炸弹。今天就从问题本质出发,给不同阶段的业务,分享 4 个能直接落地的解决方案,彻底跟 ID 耗尽危机说再见。

一、先搞懂:自增 ID 为什么会耗尽?

其实原理很简单,就两个核心矛盾:

  1. 字段类型有硬上限

    数据库自增 ID 靠整数类型存储,不同类型的上限天差地别:

  • TINYINT:最大 255(存个状态表还行,业务表用就是坑)

  • INT:最大 2147483647(约 21 亿,高增长业务 1-2 年就满)

  • BIGINT:最大 9223372036854775807(约 92 亿,无符号版更是到 1.8 万亿)

    很多团队初期没预估业务增长,随手选了INT,埋下隐患。

  1. 业务增长超预期

    比如社区类产品,初期预估日活 10 万,结果半年涨到 100 万,用户表、帖子表的数据增速远超设计,ID 自然提前耗尽。

二、4 个解决方案:覆盖所有业务场景

方案 1:新表 / 新系统 —— 用 BIGINT 一步到位,零成本防坑

这是最适合新业务、新表的 “躺平方案”,不用复杂架构,选对字段类型就够了。

具体操作:
  • 强制字段标准:所有业务表(除了配置表、状态表这种数据量 < 1000 的),主键自增 ID 统一用BIGINT UNSIGNED(无符号大整数)。

    按日均 1 亿条数据算,1.8 万亿的上限能撑近 5000 年,足够覆盖任何业务的生命周期。

  • 写进建表模板:团队里的建表 SQL 模板直接固化这个配置,比如:

CREATE TABLE \`order\` (

 \`id\` BIGINT UNSIGNED NOT NULL AUTO\_INCREMENT COMMENT '订单ID(自增主键)',

 \`order\_no\` VARCHAR(64) NOT NULL COMMENT '订单编号',

 \`user\_id\` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',

 \`amount\` DECIMAL(10,2) NOT NULL COMMENT '订单金额',

 PRIMARY KEY (\`id\`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
  • 禁用小类型:明确规定不能用TINYINTSMALLINT做自增主键,INT也只允许用在数据量明确不超 40 亿的特殊场景(比如内部管理表)。
适用场景:新系统搭建、新表创建
优势:零改造、零运维成本,一次配置终身无忧。

方案 2:分布式 / 微服务 —— 用分布式 ID,突破单表限制

当业务走到微服务、跨库跨表阶段,单库自增 ID 不仅有耗尽风险,还会出现 “跨库 ID 重复” 的问题。这时分布式 ID 就是完美选择。

主流分布式 ID 方案对比(附选型建议):
方案 原理 优点 注意点 适合场景
雪花算法 64 位 ID:1 位符号 + 41 位时间戳 + 10 位机器 ID+12 位序列号 性能高(单机每秒百万级)、无依赖 需处理时钟回拨(用 hutool 工具可规避) 中小微服务、高并发场景
美团 Leaf 号段模式(预分配 ID 区间)+ 双 buffer 缓存 稳定、支持容灾、可动态调整号段 需部署独立服务,有运维成本 中大型业务、对稳定性要求高
阿里云 UID 优化版雪花算法,支持自定义时间戳位宽 云原生、无需自建服务 依赖阿里云生态 阿里云用户
具体操作(以雪花算法为例):
  1. 引入工具类:用 hutool 的Snowflake工具,不用自己写算法:
// 初始化(机器ID:0-31,数据中心ID:0-31,避免重复)

Snowflake snowflake = IdUtil.createSnowflake(1, 1);

// 生成ID

long orderId = snowflake.nextId();
  1. 数据库配置:数据库主键字段仍用BIGINT,但关闭自增(去掉AUTO_INCREMENT),由应用层生成 ID 后写入。

  2. 验证唯一性:加一层数据库唯一索引兜底,避免极端情况下的 ID 重复。

适用场景:微服务、跨库跨表、高并发业务
优势:ID 无上限,支持分布式架构,还能解决跨库 ID 重复问题。

方案 3:历史系统 / 老表 —— 字段升级 + 数据归档,无痛改造

最棘手的情况:老表用了 INT 类型,ID 快满了,但业务不能停。这时 “字段升级 + 数据归档” 是最低侵入的方案。

实施步骤(以 MySQL 为例,零业务中断):
  1. 前置准备
  • 全量备份数据(用mysqldump或云数据库的备份功能,防止操作失误);

  • 确认 MySQL 版本:8.0 及以上支持 “在线 DDL”,改字段类型不锁表;5.7 及以下要在凌晨低峰期操作。

  1. 升级字段类型:执行 SQL 把INT改成BIGINT UNSIGNED
\-- 先查当前自增ID值,确认没超INT上限(超了需先处理)

SELECT AUTO\_INCREMENT FROM INFORMATION\_SCHEMA.TABLES WHERE TABLE\_NAME='old\_order' AND TABLE\_SCHEMA='your\_db';

\-- 升级字段类型(MySQL 8.0可在线执行)

ALTER TABLE old\_order MODIFY COLUMN id BIGINT UNSIGNED NOT NULL AUTO\_INCREMENT;
  1. 可选:数据归档:如果表数据量超 10 亿,升级后可把 3 年前的历史数据归档到冷备库(比如 Hive、低成本的云数据库),减轻主表压力:
\-- 1. 创建归档表

CREATE TABLE old\_order\_archive LIKE old\_order;

\-- 2. 迁移历史数据(分批次,每次10万条,避免锁表)

INSERT INTO old\_order\_archive SELECT \* FROM old\_order WHERE create\_time < '2021-01-01' LIMIT 100000;

\-- 3. 删除主表历史数据

DELETE FROM old\_order WHERE create\_time < '2021-01-01' LIMIT 100000;
适用场景:存量老表、ID 即将耗尽、业务不能中断
优势:改造周期短(1-2 小时),对用户无感知,不用重构业务逻辑。

方案 4:超大量数据(日志 / 物联网)—— 水平分表 + ID 重置,彻底解耦

当数据量达到千亿级、万亿级(比如物联网设备日志、大型平台的流水表),即使是BIGINT也有长期风险。这时 “水平分表 + ID 重置” 是唯一解法。

具体操作:
  1. 选对分表维度
  • 时间维度:日志表按 “月” 分表(如device_log_202401device_log_202402);

  • 哈希维度:用户流水表按 “user_id%100” 分表(如user_flow_00user_flow_99)。

  1. 子表 ID 独立自增
  • 每个子表的主键都用BIGINT UNSIGNED AUTO_INCREMENT,且自增起始值从 1 开始;

  • 比如 2024 年 1 月的日志表 ID 从 1 涨到 10 亿,2 月的表 ID 重新从 1 开始,完全不冲突。

  1. 用中间件简化路由:不用自己写分表逻辑,用 Sharding-JDBC 或 MyCat,配置分表规则即可:
\# Sharding-JDBC配置示例(按月份分表)

spring:

 shardingsphere:

   rules:

     sharding:

       tables:

         device\_log:

           actual-data-nodes: ds0.device\_log\_\${202401..202412}

           table-strategy:

             standard:

               sharding-column: create\_time

               sharding-algorithm-name: device\_log\_inline

       sharding-algorithms:

         device\_log\_inline:

           type: INLINE

           props:

             algorithm-expression: device\_log\_\${date\_format(create\_time,'yyyyMM')}
适用场景:千亿级日志表、物联网数据、万亿级流水表
优势:ID 增长完全分散,子表永不会耗尽,还能提升查询性能(单表数据量减小)。

三、避坑指南:3 个关键注意点

  1. 提前监控预警:不管用哪个方案,都要加监控 —— 当自增 ID 用到字段上限的 80% 时,触发钉钉 / 企业微信告警。监控 SQL 参考:
SELECT

 TABLE\_NAME,

 AUTO\_INCREMENT AS current\_id,

 COLUMN\_TYPE,

 \-- 计算使用占比(以BIGINT UNSIGNED为例)

 CONCAT(ROUND(AUTO\_INCREMENT/18446744073709551615\*100,2),'%') AS use\_rate

FROM INFORMATION\_SCHEMA.TABLES

JOIN INFORMATION\_SCHEMA.COLUMNS

 ON TABLES.TABLE\_NAME=COLUMNS.TABLE\_NAME

 AND TABLES.TABLE\_SCHEMA=COLUMNS.TABLE\_SCHEMA

WHERE

 TABLES.TABLE\_SCHEMA='your\_db'&#x20;

 AND COLUMNS.COLUMN\_KEY='PRI'&#x20;

 AND COLUMNS.EXTRA LIKE '%auto\_increment%';
  1. 改字段前必备份:历史表升级字段类型时,一定要先全量备份,避免因 SQL 写错、数据库异常导致数据丢失。

  2. 不要过度设计:新业务别一上来就上分布式 ID、分表,先用BIGINT,等业务增长到需要分布式架构时再升级,避免不必要的复杂度。

四、总结:按业务阶段选方案

业务阶段 推荐方案 核心目标
新表 / 新系统 BIGINT UNSIGNED 零成本防坑
微服务 / 分布式 分布式 ID(雪花算法 / Leaf) 支持跨库,无 ID 上限
老表 / 历史系统 字段升级 + 数据归档 低侵入改造,不中断业务
千亿级数据场景 水平分表 + ID 重置 彻底解耦 ID 增长,提性能

自增 ID 耗尽不是 “小概率事件”,而是业务增长到一定阶段的必然挑战。与其等出故障后紧急救火,不如提前选对方案 —— 毕竟,预防的成本永远比补救低。

建议大家这一周内,先查一下自己业务库里所有自增主键的字段类型和使用占比,把风险扼杀在萌芽里~

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐