MySQL 自增主键用完了怎么办?
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
-
数据类型理解:面试官不仅仅是想知道 "用完了怎么办",更是想考察你是否清楚不同整数类型的取值范围,以及
INT和BIGINT的区别。 -
系统设计思维:考察你是否具备前瞻性的系统设计能力,能否在表设计阶段就考虑到数据量增长的问题。
-
分布式 ID 方案:当单机自增主键不够用时,是否了解分布式 ID 生成方案(雪花算法、UUID 等),这是高并发系统的必备知识。
核心答案
自增主键用完的后果:当自增 ID 达到数据类型的最大值后,再次插入数据会报错 Duplicate entry 'xxx' for key 'PRIMARY',新的数据无法写入。
各整数类型的主键容量:
| 数据类型 | 字节数 | 最大值 | 容量级 |
|---|---|---|---|
TINYINT | 1 | 127(有符号)/ 255(无符号) | 百级 |
SMALLINT | 2 | 32767 / 65535 | 万级 |
MEDIUMINT | 3 | 8388607 / 16777215 | 百万级 |
INT | 4 | 2147483647 / 4294967295 | 21 亿 / 42 亿 |
BIGINT | 8 | 9223372036854775807 / ... | 922 亿亿 |
一句话总结:主键用完会导致插入失败,解决方案有 改用更大的类型(BIGINT) 或 使用分布式 ID(雪花算法)。
深度解析
一、自增主键用完会发生什么?
当自增 ID 达到上限后,MySQL 的行为如下:
上图展示了自增主键用完后的情况。关键点说明:
- 自增到最大值:当
AUTO_INCREMENT达到数据类型的上限时,无法继续递增 - 插入失败:MySQL 尝试使用最大值插入,但该值已存在,导致主键冲突
- 删除不回退:即使删除了中间的记录,
AUTO_INCREMENT值也不会变小 - 影响范围:所有依赖自增主键的插入操作都会失败
二、解决方案一:改用更大的数据类型
最简单直接的方案:将 INT 改为 BIGINT。
-- 修改前:INT 最大约 21 亿(有符号)或 42 亿(无符号)
CREATE TABLE orders (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
order_no VARCHAR(32)
);
-- 修改后:BIGINT 最大约 922 亿亿
ALTER TABLE orders MODIFY id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY;
BIGINT 有多大?
注意事项:
- 修改字段类型会锁表,大表操作需要谨慎(使用
pt-online-schema-change等工具) - 建议新表设计时就直接使用
BIGINT,省去后续迁移的麻烦
三、解决方案二:使用分布式 ID —— 雪花算法(Snowflake)
对于分布式系统,单机自增主键不够用或存在冲突风险,推荐使用 雪花算法 生成分布式 ID。
上图展示了雪花算法的 ID 结构。核心优势在于:
- 全局唯一:通过机器 ID 区分不同节点,保证分布式环境下的唯一性
- 趋势递增:基于时间戳生成,ID 大致按时间递增,利于索引
- 高性能:本地生成,不依赖数据库,单机每秒可生成 400 万个 ID
- 无需协调:各节点独立生成,不需要分布式锁或中心化服务
Java 实现示例:
public class SnowflakeIdGenerator {
// 起始时间戳(2024-01-01)
private final long twepoch = 1704038400000L;
// 机器 ID 所占位数
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
// 序列号所占位数
private final long sequenceBits = 12L;
// 机器 ID 最大值:31
private final long maxWorkerId = ~(-1L << workerIdBits);
// 序列号最大值:4095
private final long sequenceMask = ~(-1L << sequenceBits);
// 机器 ID 左移位数
private final long workerIdShift = sequenceBits;
// 时间戳左移位数
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long workerId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long workerId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("worker Id error");
}
this.workerId = workerId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
// 时钟回拨检测
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards!");
}
// 同一毫秒内,序列号递增
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 序列号溢出,等待下一毫秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// 组装 ID
return ((timestamp - twepoch) << timestampLeftShift)
| (workerId << workerIdShift)
| sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
四、各种 ID 生成方案对比
为什么 UUID 不推荐作为主键?
- 页分裂问题:UUID 是无序的,插入时会导致 B+ 树频繁分裂
- 存储空间大:36 字符 vs 8 字节的 BIGINT
- 索引效率低:无序 ID 导致缓存命中率下降
五、最佳实践建议
1. 新表设计直接用 BIGINT
-- 推荐写法
CREATE TABLE orders (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
-- 其他字段...
) ENGINE=InnoDB;
2. 分布式系统用雪花算法
// 使用成熟的开源实现
// 如:Mybatis-Plus 的 IdWorker、百度 UidGenerator、美团 Leaf
@IdType(type = IdType.ASSIGN_ID) // Mybatis-Plus 雪花算法
private Long id;
3. 设置合理的自增起始值和步长
-- 多主架构下,避免 ID 冲突
-- 服务器 1:起始值 1,步长 2(生成 1, 3, 5, ...)
SET GLOBAL auto_increment_offset = 1;
SET GLOBAL auto_increment_increment = 2;
-- 服务器 2:起始值 2,步长 2(生成 2, 4, 6, ...)
SET GLOBAL auto_increment_offset = 2;
SET GLOBAL auto_increment_increment = 2;
面试高频追问
-
追问一:为什么 UUID 不适合做主键?
- 答:UUID 无序会导致 B+ 树频繁页分裂,影响插入性能;同时占用空间大(36 字符 vs 8 字节),降低索引效率。
-
追问二:雪花算法有什么缺点?
- 答:依赖服务器时钟,时钟回拨会导致 ID 重复或异常;需要处理机器 ID 的分配问题(可通过 ZooKeeper/Redis 统一管理)。
-
追问三:INT 和 BIGINT 对性能有影响吗?
- 答:影响很小。主键索引的查找时间复杂度是 O(log n),与主键大小关系不大;但 BIGINT 占用更多存储空间,可能影响内存中的缓存效率。
常见面试变体
- "MySQL 主键用完了会怎么样?"
- "为什么推荐用 BIGINT 做主键?"
- "分布式 ID 生成方案有哪些?"
- "雪花算法的原理是什么?"
记忆口诀
主键 ID 方案选择:
- 单机用 BIGINT:容量够大,简单可靠
- 分布式用雪花:全局唯一,趋势递增
- 远离 UUID:无序导致页分裂
总结
自增主键用完会导致插入失败,解决方案有两种:单机场景改用 BIGINT(约 922 亿亿容量,足够使用);分布式场景使用雪花算法生成全局唯一、趋势递增的 ID。新表设计建议直接用 BIGINT UNSIGNED,避免后续迁移成本。