数据库自增 ID 耗尽?4 个落地级方案,从新表到老系统全覆盖
上周看到一个紧急求助:他们电商平台的订单表突然无法插入数据,报错 “Duplicate entry for key ‘PRIMARY’”,排查后发现 —— 自增 ID 用完了。原来初期图省事用了INT
类型,日均 100 万订单的量,不到 2 年就触达了 21 亿的上限,直接导致业务中断 4 小时。
这不是个例。自增 ID 作为最常用的主键方案,看似稳定,实则藏着 “耗尽” 的隐形炸弹。今天就从问题本质出发,给不同阶段的业务,分享 4 个能直接落地的解决方案,彻底跟 ID 耗尽危机说再见。
一、先搞懂:自增 ID 为什么会耗尽?
其实原理很简单,就两个核心矛盾:
-
字段类型有硬上限
数据库自增 ID 靠整数类型存储,不同类型的上限天差地别:
-
TINYINT
:最大 255(存个状态表还行,业务表用就是坑) -
INT
:最大 2147483647(约 21 亿,高增长业务 1-2 年就满) -
BIGINT
:最大 9223372036854775807(约 92 亿,无符号版更是到 1.8 万亿)很多团队初期没预估业务增长,随手选了
INT
,埋下隐患。
-
业务增长超预期
比如社区类产品,初期预估日活 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='订单表';
- 禁用小类型:明确规定不能用
TINYINT
、SMALLINT
做自增主键,INT
也只允许用在数据量明确不超 40 亿的特殊场景(比如内部管理表)。
适用场景:新系统搭建、新表创建
优势:零改造、零运维成本,一次配置终身无忧。
方案 2:分布式 / 微服务 —— 用分布式 ID,突破单表限制
当业务走到微服务、跨库跨表阶段,单库自增 ID 不仅有耗尽风险,还会出现 “跨库 ID 重复” 的问题。这时分布式 ID 就是完美选择。
主流分布式 ID 方案对比(附选型建议):
方案 | 原理 | 优点 | 注意点 | 适合场景 |
---|---|---|---|---|
雪花算法 | 64 位 ID:1 位符号 + 41 位时间戳 + 10 位机器 ID+12 位序列号 | 性能高(单机每秒百万级)、无依赖 | 需处理时钟回拨(用 hutool 工具可规避) | 中小微服务、高并发场景 |
美团 Leaf | 号段模式(预分配 ID 区间)+ 双 buffer 缓存 | 稳定、支持容灾、可动态调整号段 | 需部署独立服务,有运维成本 | 中大型业务、对稳定性要求高 |
阿里云 UID | 优化版雪花算法,支持自定义时间戳位宽 | 云原生、无需自建服务 | 依赖阿里云生态 | 阿里云用户 |
具体操作(以雪花算法为例):
- 引入工具类:用 hutool 的
Snowflake
工具,不用自己写算法:
// 初始化(机器ID:0-31,数据中心ID:0-31,避免重复)Snowflake snowflake = IdUtil.createSnowflake(1, 1);// 生成IDlong orderId = snowflake.nextId();
-
数据库配置:数据库主键字段仍用
BIGINT
,但关闭自增(去掉AUTO_INCREMENT
),由应用层生成 ID 后写入。 -
验证唯一性:加一层数据库唯一索引兜底,避免极端情况下的 ID 重复。
适用场景:微服务、跨库跨表、高并发业务
优势:ID 无上限,支持分布式架构,还能解决跨库 ID 重复问题。
方案 3:历史系统 / 老表 —— 字段升级 + 数据归档,无痛改造
最棘手的情况:老表用了 INT 类型,ID 快满了,但业务不能停。这时 “字段升级 + 数据归档” 是最低侵入的方案。
实施步骤(以 MySQL 为例,零业务中断):
- 前置准备:
-
全量备份数据(用
mysqldump
或云数据库的备份功能,防止操作失误); -
确认 MySQL 版本:8.0 及以上支持 “在线 DDL”,改字段类型不锁表;5.7 及以下要在凌晨低峰期操作。
- 升级字段类型:执行 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;
- 可选:数据归档:如果表数据量超 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 重置” 是唯一解法。
具体操作:
- 选对分表维度:
-
时间维度:日志表按 “月” 分表(如
device_log_202401
、device_log_202402
); -
哈希维度:用户流水表按 “user_id%100” 分表(如
user_flow_00
到user_flow_99
)。
- 子表 ID 独立自增:
-
每个子表的主键都用
BIGINT UNSIGNED AUTO_INCREMENT
,且自增起始值从 1 开始; -
比如 2024 年 1 月的日志表 ID 从 1 涨到 10 亿,2 月的表 ID 重新从 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\_timesharding-algorithm-name: device\_log\_inlinesharding-algorithms:device\_log\_inline:type: INLINEprops:algorithm-expression: device\_log\_\${date\_format(create\_time,'yyyyMM')}
适用场景:千亿级日志表、物联网数据、万亿级流水表
优势:ID 增长完全分散,子表永不会耗尽,还能提升查询性能(单表数据量减小)。
三、避坑指南:3 个关键注意点
- 提前监控预警:不管用哪个方案,都要加监控 —— 当自增 ID 用到字段上限的 80% 时,触发钉钉 / 企业微信告警。监控 SQL 参考:
SELECTTABLE\_NAME,AUTO\_INCREMENT AS current\_id,COLUMN\_TYPE,\-- 计算使用占比(以BIGINT UNSIGNED为例)CONCAT(ROUND(AUTO\_INCREMENT/18446744073709551615\*100,2),'%') AS use\_rateFROM INFORMATION\_SCHEMA.TABLESJOIN INFORMATION\_SCHEMA.COLUMNSON TABLES.TABLE\_NAME=COLUMNS.TABLE\_NAMEAND TABLES.TABLE\_SCHEMA=COLUMNS.TABLE\_SCHEMAWHERETABLES.TABLE\_SCHEMA='your\_db' AND COLUMNS.COLUMN\_KEY='PRI' AND COLUMNS.EXTRA LIKE '%auto\_increment%';
-
改字段前必备份:历史表升级字段类型时,一定要先全量备份,避免因 SQL 写错、数据库异常导致数据丢失。
-
不要过度设计:新业务别一上来就上分布式 ID、分表,先用
BIGINT
,等业务增长到需要分布式架构时再升级,避免不必要的复杂度。
四、总结:按业务阶段选方案
业务阶段 | 推荐方案 | 核心目标 |
---|---|---|
新表 / 新系统 | BIGINT UNSIGNED | 零成本防坑 |
微服务 / 分布式 | 分布式 ID(雪花算法 / Leaf) | 支持跨库,无 ID 上限 |
老表 / 历史系统 | 字段升级 + 数据归档 | 低侵入改造,不中断业务 |
千亿级数据场景 | 水平分表 + ID 重置 | 彻底解耦 ID 增长,提性能 |
自增 ID 耗尽不是 “小概率事件”,而是业务增长到一定阶段的必然挑战。与其等出故障后紧急救火,不如提前选对方案 —— 毕竟,预防的成本永远比补救低。
建议大家这一周内,先查一下自己业务库里所有自增主键的字段类型和使用占比,把风险扼杀在萌芽里~