1. 一个看似简单却暗藏玄机的设计

去年我们电商系统遇到个诡异问题:订单号的末四位经常重复。经过排查发现,订单表的自增ID竟然在某个分片里出现了循环跳跃。这让我意识到,很多人把AUTO_INCREMENT当作"傻瓜式"配置,却不知道它的不合理设置可能引发严重生产事故。

2. 自增ID的四种典型"病态"表现

2.1 数值溢出(经典必考题)

-- 创建测试表(MySQL 5.7)
CREATE TABLE expired_contracts (
    id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,  -- 最大255
    contract_no VARCHAR(20)
) AUTO_INCREMENT=250;

-- 连续插入三条记录
INSERT INTO expired_contracts (contract_no) VALUES ('CT001');
INSERT INTO expired_contracts (contract_no) VALUES ('CT002');  -- ID=251
INSERT INTO expired_contracts (contract_no) VALUES ('CT003');  -- 报错:超出TINYINT范围

2.2 步长失控(分库分表的噩梦)

-- 设置全局自增步长为10(MySQL 8.0)
SET @@global.auto_increment_increment=10;

-- 创建分表
CREATE TABLE user_0 (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50)
);

-- 插入测试数据
INSERT INTO user_0 (name) VALUES ('张三'),('李四');  -- ID将变为1,11

2.3 黑洞跳跃(数据删除后的幽灵)

-- 创建日志表
CREATE TABLE error_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    content TEXT
) AUTO_INCREMENT=1000;

-- 批量插入后删除
INSERT INTO error_logs (content) 
VALUES ('error1'),('error2'),('error3');  -- ID:1000-1002
DELETE FROM error_logs WHERE id=1002;

-- 新插入数据
INSERT INTO error_logs (content) VALUES ('error4');  -- ID=1003,产生数字空洞

2.4 分片碰撞(分布式系统的定时炸弹)

-- 分库1的表结构
CREATE TABLE order_01 (
    id INT AUTO_INCREMENT PRIMARY KEY,
    order_no VARCHAR(20)
) AUTO_INCREMENT=1;

-- 分库2的表结构 
CREATE TABLE order_02 (
    id INT AUTO_INCREMENT PRIMARY KEY,
    order_no VARCHAR(20)
) AUTO_INCREMENT=10000;

-- 当分库数量扩展到10个时,第11个分库的初始值设置就会出问题

3. 六大手术方案深度解析

3.1 字段扩容术(适合早期干预)

-- 修改字段类型(MySQL 5.6+在线DDL)
ALTER TABLE user_behavior_log 
MODIFY id BIGINT UNSIGNED AUTO_INCREMENT;
-- 执行时间:200万数据约需30秒(需验证当前版本是否支持INSTANT算法)

3.2 重置自增术(适合数据归档场景)

-- 先获取当前最大值
SELECT MAX(id) FROM archived_data;  -- 假设得到50000

-- 调整自增起点(注意需要+1)
ALTER TABLE archived_data AUTO_INCREMENT=50001;

-- 验证设置
SHOW CREATE TABLE archived_data;  -- 查看AUTO_INCREMENT值

3.3 步长调控术(分布式架构必会)

-- 设置双主复制环境下的自增配置(MySQL 8.0)
SET @@global.auto_increment_increment=2;
SET @@global.auto_increment_offset=1;

-- 验证插入效果
INSERT INTO shard_table (name) VALUES ('node1');  -- ID=1,3,5...
-- 另一个节点配置offset=2,获得ID=2,4,6...

3.4 序列替代术(PostgreSQL思路迁移)

-- 创建独立序列(MySQL 8.0+)
CREATE SEQUENCE universal_seq START WITH 1000 INCREMENT BY 1;

-- 在表中使用序列
CREATE TABLE financial_records (
    id BIGINT DEFAULT (NEXT VALUE FOR universal_seq),
    amount DECIMAL(10,2)
);

-- 混合使用场景
INSERT INTO financial_records (amount) 
VALUES (100.50);  -- 使用序列
INSERT INTO financial_records (id, amount)
VALUES (NEXT VALUE FOR universal_seq, 200.30);  -- 显式调用

3.5 分布式ID注入术(Snowflake方案实现)

-- 创建不带自增的表
CREATE TABLE distributed_orders (
    id BIGINT UNSIGNED PRIMARY KEY,
    order_info JSON
);

-- Java代码示例(简化版)
Long snowflakeId = Snowflake.generateId();  -- 生成128位ID
String sql = "INSERT INTO distributed_orders VALUES (?, ?)";
preparedStatement.setLong(1, snowflakeId);

3.6 业务号分离术(订单号≠自增ID)

-- 创建带双字段的表
CREATE TABLE smart_orders (
    id INT AUTO_INCREMENT PRIMARY KEY,  -- 内部使用
    order_no VARCHAR(20) UNIQUE,       -- 对外展示
    create_time DATETIME
);

-- 生成订单号逻辑
UPDATE sequence SET value=LAST_INSERT_ID(value+1) WHERE name='order_seq';
INSERT INTO smart_orders (order_no, create_time)
VALUES (CONCAT('DD', DATE_FORMAT(NOW(),'%Y%m%d'), LAST_INSERT_ID()), NOW());

4. 关联技术生态圈

4.1 中间件方案(MyCat实践)

<!-- 配置server.xml -->
<system>
    <property name="sequnceHandlerType">2</property>
</system>

<!-- 配置sequence_db_conf.properties -->
# 指定序列存储在哪个数据库
GLOBAL=dn1
COMPANY=dn2

4.2 在线变更工具(gh-ost实战)

gh-ost \
--alter="MODIFY id BIGINT AUTO_INCREMENT" \
--database="prod_db" \
--table="critical_table" \
--host="rm-2ze12345.mysql.rds.aliyuncs.com" \
--assume-rbr \
--initially-drop-old-table \
--max-load=Threads_running=50 \
--critical-load=Threads_running=1000 \
--chunk-size=1000 \
--user="dba" \
--password="***" \
--execute

5. 应用场景决策树

  • 单机小数据量:直接ALTER修改
  • 分布式系统:步长控制+号段分配
  • 金融交易系统:业务号分离+分布式ID
  • 物联网时序数据:自增ID+时间分片
  • 高并发写入:Redis预生成序列

6. 技术选型对照表

方案 优点 缺点 适用场景
字段扩容 一劳永逸 需要停机维护 数据量较小
分布式步长 天然适应分库 扩容困难 固定分片数量
Snowflake算法 完全分布式 时钟回拨问题 高并发分布式系统
业务号分离 解耦业务与存储 增加复杂度 对展示编号有要求

7. 血泪经验总结

去年我们处理过一个典型案例:某支付表使用INT自增ID,预计三年后溢出。通过gh-ost在线修改为BIGINT,整个过程持续45分钟,期间保持业务正常运行。关键步骤:

  1. 提前三个月监控自增ID增速
  2. 在测试环境模拟全量数据变更
  3. 使用影子表进行数据一致性验证
  4. 选择交易低谷期执行
  5. 变更完成后持续监控三天

8. 避坑指南

  • 禁止在生产环境直接执行ALTER TABLE(除非确认支持在线DDL)
  • 修改AUTO_INCREMENT前必须备份(包括元数据)
  • 分库分表场景要先停写再修改
  • 使用ROW格式二进制日志的主从架构要特别注意一致性
  • 云数据库可能需要使用特定参数(如RDS的innodb_autoinc_lock_mode)