文章目录
- 每日一句正能量
- 1. 背景与问题:AUTO_INCREMENT迁移的核心,是把“下一个ID由谁生成”迁过去
- 2. 环境与数据:先判断是“简单自增”还是“分布式编号”
- 2.1 为什么 `auto_increment_increment/offset` 必须检查
- 2.2 KingbaseES有两条主流落地路线
- 方案A:Identity
- 方案B:Sequence + Default
- 3. 复现过程:最容易踩的五个坑
- 3.1 坑一:导入3亿历史ID后,目标生成器仍从1开始
- 3.2 坑二:误以为显式插入历史ID会自动同步目标生成器
- 3.3 坑三:把主键是否连续当成验收指标
- 3.4 坑四:只测试单条INSERT,不测试批量主键回传
- 3.5 坑五:源库用多写AUTO_INCREMENT,目标却按单写设计
- 4. 方案实施:一套可执行的AUTO_INCREMENT迁移步骤
- 4.1 第一步:建立主键清单
- 4.2 第二步:处理 UNSIGNED
- 4.3 第三步:优先Identity映射
- 4.4 第四步:Sequence兜底方案
- 4.5 第五步:全量导数保留原ID
- 4.6 第六步:增量同步仍然传原ID
- 4.7 第七步:切流前二次校准生成器
- 4.8 第八步:并发回归
- 单条 INSERT
- 10/50/100并发
- 回滚
- 显式历史ID
- 4.9 第九步:批量INSERT单独验证
- 4.10 第十步:ON DUPLICATE KEY UPDATE一起扫描
- 5. 结果对比:验收必须同时看数据、生成器和应用
- 5.1 历史数据
- 5.2 外键和业务引用
- 5.3 生成器安全距离
- 5.4 应用主键回传
- 5.5 示例回归结果模板
- 5.6 性能指标
- 6. 风险与复盘:自增主键最危险的是“双主同时生成”
- 6.1 风险一:切换窗口两边都在自动生成ID
- 6.2 风险二:多库分片ID被误当单库AUTO_INCREMENT
- 6.3 风险三:Sequence CACHE造成空洞被误判数据丢失
- 6.4 风险四:历史高值异常
- 6.5 风险五:BIGINT UNSIGNED容量问题
- 6.6 风险六:LAST_INSERT_ID依赖被遗漏
- 6.7 风险七:Sequence权限遗漏
- 回退方案:回源之前也要重新校准AUTO_INCREMENT
- 最终复盘
- 附录 A:MySQL源DDL
- 附录 B:KingbaseES Identity方案
- 附录 C:KingbaseES Sequence方案
- 附录 D:最低回归清单
每日一句正能量
“人生没有标准答案,敢于重新开始的人永远自带光芒。”
每个人都可以书写自己的答案。而“重新开始”的能力,是一个人生命力最璀璨的证明。跌倒后能爬起,归零后能重启,这种勇气本身就是一种无法被忽视的光芒。
主题:AUTO_INCREMENT 兼容 / MySQL → KingbaseES / 互联网业务迁移
重点:DDL 转换、Identity/Sequence 选择、历史 ID 保留、并发写入、主键回传、回归测试、数据校验与回退
适用场景:订单、用户、支付流水、内容主表、互联网业务单库或分片库迁移。
1. 背景与问题:AUTO_INCREMENT迁移的核心,是把“下一个ID由谁生成”迁过去
MySQL 互联网业务里最常见的主键定义之一:
CREATETABLEbiz_order(order_idBIGINTNOTNULLAUTO_INCREMENT,...PRIMARYKEY(order_id));很多迁移方案会直接写:
AUTO_INCREMENT → Identity然后认为工作完成。
但自增主键真正影响的不是一行 DDL,而是一整条写入链路:
INSERT不传ID → 数据库生成ID → 驱动/ORM拿回ID → 子表/消息使用这个ID → CDC传播 → 下一条记录继续生成对互联网业务还可能存在:
auto_increment_increment auto_increment_offset 多主写入 分库分表 号段 雪花ID 外部ID服务因此迁移前必须先判断:
源系统到底是在使用 MySQL 的“单实例 AUTO_INCREMENT”,还是借用了 AUTO_INCREMENT 做多写节点的号段隔离。
MySQL 8.4 官方说明,InnoDB 为 AUTO_INCREMENT 维护专门计数器;不显式提供值时,由计数器产生新值。如果显式插入的 ID 大于当前计数器,后续计数器会被推进。MySQL 同时提供auto_increment_increment和auto_increment_offset,可以用于多服务器生成互不冲突的自增值。
所以迁移目标必须覆盖:
历史ID不变 目标新ID不冲突 应用主键回传正常 并发写入安全 多写架构语义不丢失 异常时能回退2. 环境与数据:先判断是“简单自增”还是“分布式编号”
示例环境:
源库:MySQL 8.0/8.4 InnoDB 目标:KingbaseES V9 业务:互联网订单服务 单表数据:约3亿 日新增:300万 主键:BIGINT AUTO_INCREMENT 写入方式:JDBC/MyBatis源表:
CREATETABLEbiz_order(order_idBIGINTNOTNULLAUTO_INCREMENT,customer_idBIGINTNOTNULL,order_noVARCHAR(64)NOTNULL,amountDECIMAL(18,2)NOTNULL,created_atDATETIME(6)NOTNULL,PRIMARYKEY(order_id),UNIQUEKEYuk_order_no(order_no));迁移前至少记录:
当前MAX(order_id) AUTO_INCREMENT当前起点 数据类型是否UNSIGNED auto_increment_increment auto_increment_offset 主库数量 是否多主写 应用如何取得新ID 是否有显式插入ID逻辑2.1 为什么auto_increment_increment/offset必须检查
MySQL 官方文档说明:
auto_increment_increment控制每次自增的步长;
auto_increment_offset控制起始偏移。
比如两个写节点:
节点A:offset=1, increment=2 → 1,3,5,7... 节点B:offset=2, increment=2 → 2,4,6,8...如果这种架构迁移到 KingbaseES 后简单变成:
所有节点共用一个 START 1 INCREMENT 1虽然不会一定产生重复,但源系统的分布式编号策略已经改变。
如果应用或分片逻辑依赖:
id % 2判断来源节点,就会出现业务问题。
因此要先做“主键架构识别”。
2.2 KingbaseES有两条主流落地路线
当前 KingbaseES SQL 参考明确支持:
GENERATED ALWAYSASIDENTITYGENERATEDBYDEFAULTASIDENTITYIdentity 列会绑定一个隐式序列,新插入行可以自动获得值。
另外 KingbaseES 还提供独立:
CREATESEQUENCE支持:
START WITH INCREMENT BY CACHE NO CYCLE NEXTVAL CURRVAL SETVAL因此最常用两条路线:
方案A:Identity
order_idBIGINTGENERATEDBYDEFAULTASIDENTITY适合:
单写服务 传统AUTO_INCREMENT表 希望DDL简单方案B:Sequence + Default
CREATESEQUENCE biz_order_id_seq;order_idBIGINTDEFAULTNEXTVAL('biz_order_id_seq')适合:
需要显式管理生成器 需要更清楚控制START/INCREMENT/CACHE 迁移工具对Identity支持一般3. 复现过程:最容易踩的五个坑
3.1 坑一:导入3亿历史ID后,目标生成器仍从1开始
源端:
MAX(order_id)=386,120,008全量迁移:
order_id原值全部保留目标数据检查:
COUNT一致 MAX一致如果 Identity/Sequence 仍然从:
1开始,切流后就有主键冲突风险。
因此切换前必须满足:
目标NEXT VALUE > 全局已使用MAX(id)这应该是自动化阻断条件,而不是检查清单里“人工看一眼”。
3.2 坑二:误以为显式插入历史ID会自动同步目标生成器
MySQL 的行为很容易形成惯性。
MySQL 官方示例说明,如果向 AUTO_INCREMENT 列显式写一个较大的值,例如:
100随后自动生成的值可以从:
101继续。
因此 DBA 容易形成:
“我已经导入了历史ID,目标自增肯定也知道最大值。”
但 KingbaseES 的 Identity 依赖隐式序列;BY DEFAULT允许显式历史值优先,不代表迁移程序就可以省略生成器校准。
正确做法仍然是:
导完数据 → SELECT MAX(id) → 检查Identity/Sequence当前状态 → 显式RESTART/SETVAL/ALTER3.3 坑三:把主键是否连续当成验收指标
MySQL AUTO_INCREMENT 在:
事务回滚 并发插入 失败插入 批量语句场景下并不应该被当作业务连续号码。
KingbaseES Sequence 也一样。
官方开发规范明确建议:
不要将商业逻辑建立在序列完全连续性上并说明增大 CACHE 可以减少争用,但会增加不连续的可能。
所以验收重点是:
唯一 不会回退到已使用范围 并发安全不是:
1001、1002、1003一个都不能少订单号、发票号如果要求特定连续规则,应使用独立业务编号机制。
3.4 坑四:只测试单条INSERT,不测试批量主键回传
MySQL 生态中很多框架依赖:
LAST_INSERT_ID() getGeneratedKeys() useGeneratedKeysMySQL 官方文档也明确提供LAST_INSERT_ID()/mysql_insert_id()获取最近自动生成的 AUTO_INCREMENT 值。
迁到 KingbaseES 后,数据库生成 ID 没问题,不代表:
JDBC MyBatis JPA 批量INSERT都能用完全相同方式拿到主键。
尤其批量插入:
INSERTINTO...VALUES(...),(...),(...);不能只假设:
拿到第一个ID → 后面的ID必然连续推导因为这种假设把“生成器连续性”当成了应用协议。
应该让真实驱动/ORM返回并验证每个生成主键。
3.5 坑五:源库用多写AUTO_INCREMENT,目标却按单写设计
MySQL FAQ 明确说明,MySQL 本身没有通用 Sequence,但可以通过:
auto_increment_increment auto_increment_offset在多服务器场景减少 AUTO_INCREMENT 冲突。
如果源系统:
双主 多源复制 多机房迁移时必须回答:
目标还是多写吗?如果目标变成:
单主写可以把主键生成收敛到单一 Sequence/Identity。
如果目标仍然需要:
多节点独立生成ID则要重新设计:
不同START/OFFSET的Sequence 号段 全局ID服务 雪花ID不能只把源表 DDL 翻译一下。
4. 方案实施:一套可执行的AUTO_INCREMENT迁移步骤
4.1 第一步:建立主键清单
建议 SQL 清单至少记录:
schema table column type unsigned current_max_id auto_increment increment offset foreign_key_count write_qps id_generation_mode分类:
S1:单库单写AUTO_INCREMENT S2:多写increment/offset S3:分库分表号段 S4:外部ID/雪花 S5:业务显式赋ID优先迁:
S1复杂度最低。
4.2 第二步:处理 UNSIGNED
MySQL 常见:
BIGINTUNSIGNEDAUTO_INCREMENT这不仅是自增问题,也是数据类型范围问题。
目标 KingbaseES 如果采用有符号BIGINT,必须检查:
MAX(id)是否已经超过目标类型上限。
大部分业务实际值远低于上限,但迁移评估不能靠猜。
必须:
MAX(id) + 未来增长年限做容量评估。
4.3 第三步:优先Identity映射
典型目标:
CREATETABLEbiz_order(order_idBIGINTGENERATEDBYDEFAULTASIDENTITY(STARTWITH1INCREMENTBY1)PRIMARYKEY,customer_idBIGINTNOTNULL,order_noVARCHAR(64)NOTNULLUNIQUE,amountNUMERIC(18,2)NOTNULL,created_atTIMESTAMP(6)NOTNULL);为什么使用:
BY DEFAULT而不是迁移阶段直接ALWAYS?
KingbaseES 官方语义是:
BY DEFAULT: 用户显式提供值时,用户值优先 ALWAYS: 默认强制使用生成值,除非显式覆盖系统值迁移全量和增量都需要保留 MySQL 历史 ID,因此BY DEFAULT更方便。
切换完成后是否调整更严格的写入策略,可以通过:
应用层禁止传ID 权限 SQL审计实现。
4.4 第四步:Sequence兜底方案
如果想把生成器独立出来:
CREATESEQUENCE biz_order_id_seqASBIGINTSTARTWITH386120009INCREMENTBY1CACHE100NOCYCLE;列:
order_idBIGINTDEFAULTNEXTVAL('biz_order_id_seq')KingbaseES 官方序列文档明确支持START WITH、INCREMENT BY、CACHE、NO CYCLE,并可通过nextval/currval/setval管理生成器。
Sequence 方案的工程优势:
生成器独立可见 参数更容易审计 多表/特殊生成策略可重用 迁移脚本容易校准缺点:
DDL比Identity多一个对象 权限要单独确认 对象命名和生命周期要治理4.5 第五步:全量导数保留原ID
历史订单:
1 2 ... 386120008目标必须仍然是:
1 2 ... 386120008不要重新生成。
原因:
order_detail.order_id payment.order_id message业务键 数据仓库引用 日志关联都可能已经依赖原ID。
如果重新编号,就会把“主表迁移”变成“全链路主键重映射项目”。
4.6 第六步:增量同步仍然传原ID
全量迁移运行几个小时甚至几天时,MySQL 仍有新订单写入。
CDC 增量:
INSERT order_id=386120009目标也要:
INSERT order_id=386120009而不是由 KingbaseES 自己生成一个 ID。
迁移期:
MySQL是主键权威切流后:
KingbaseES才成为新的主键权威这个切换点必须明确。
4.7 第七步:切流前二次校准生成器
第一次全量导完:
MAX(id)=386120008几小时后 CDC 已经追到:
386520100所以不能只在全量后校准一次。
切流正确顺序:
停止源端新写 ↓ 追平最后CDC ↓ 源目标MAX(id)比对 ↓ 目标生成器再次校准 ↓ 验证NEXT VALUE安全 ↓ 开启KingbaseES写4.8 第八步:并发回归
至少测试:
单条 INSERT
INSERTINTObiz_order(customer_id,order_no,amount)VALUES(...);断言:
生成ID非NULL 数据库行ID = 应用拿到ID10/50/100并发
检查:
duplicate=0 PK conflict=0 generated ids unique回滚
BEGIN INSERT ROLLBACK允许:
ID出现空洞但后续不得产生重复。
显式历史ID
分别插入:
比当前MAX低 比当前MAX高确认迁移流程和后续校准脚本都能正确处理。
4.9 第九步:批量INSERT单独验证
MySQL 应用很喜欢:
INSERTINTOt(...)VALUES(...),(...),(...);迁移后至少验证:
生成多少个ID 驱动返回多少个 顺序是否对应输入行 批次部分失败如何处理不要用:
first_id + i推算后续主键作为正式设计。
4.10 第十步:ON DUPLICATE KEY UPDATE一起扫描
互联网 MySQL 常见:
INSERT...ONDUPLICATEKEYUPDATE...MySQL 官方文档说明,它遇到 UNIQUE/PRIMARY KEY 冲突时可以转为 UPDATE;在含 AUTO_INCREMENT 的表上,它还会影响自增值和LAST_INSERT_ID()相关行为。
因此迁移自增主键时应该顺便扫描:
ON DUPLICATE KEY UPDATE REPLACE INTO INSERT IGNORE LAST_INSERT_ID这些都属于“写入语义”,不能只改表结构。
5. 结果对比:验收必须同时看数据、生成器和应用
5.1 历史数据
至少比较:
COUNT(*)MIN(id)MAX(id)COUNT(DISTINCTid)断言:
COUNT = COUNT(DISTINCT id)主键无重复。
5.2 外键和业务引用
例如:
SELECTCOUNT(*)FROMorder_detail dLEFTJOINbiz_order oONd.order_id=o.order_idWHEREo.order_idISNULL;结果:
05.3 生成器安全距离
迁移完成:
MAX(id)=386520100目标下一值必须:
>386520100如果采用多节点号段,还要验证:
所有节点未来生成空间互不冲突5.4 应用主键回传
测试:
JDBC getGeneratedKeys MyBatis useGeneratedKeys JPA @GeneratedValue 批量Insert 事务Insert必须断言:
应用对象ID = 数据库实际ID5.5 示例回归结果模板
| 用例 | MySQL | KingbaseES | 结果 |
|---|---|---|---|
| 单条自动ID | 成功 | 成功 | 通过 |
| 显式历史ID | 保留 | 保留 | 通过 |
| 50并发 | 无重复 | 无重复 | 通过 |
| ROLLBACK后继续插入 | 允许空洞 | 允许空洞 | 通过 |
| JDBC取主键 | 正常 | 正常 | 通过 |
| 批量Insert | 返回ID | 已验证返回 | 通过 |
这些是验收模板,不是本文声称的真实生产结果。
5.6 性能指标
自增迁移还应该记录:
Insert TPS P50/P95/P99 生成器等待 WAL 索引写入 Sequence CACHE大小KingbaseES 官方开发规范建议适当增大 Sequence CACHE 可以降低争用,但同时会增加序列不连续性。
互联网高并发业务可以测试:
CACHE 1 CACHE 20 CACHE 100 CACHE 300选择性能和可接受空洞之间的平衡。
再次强调:
主键唯一远比:
主键连续重要。
6. 风险与复盘:自增主键最危险的是“双主同时生成”
6.1 风险一:切换窗口两边都在自动生成ID
这是最大的风险。
如果:
MySQL继续AUTO_INCREMENT + KingbaseES Identity也开放两个库可能在相同数值空间生成新ID。
所以切换要保证:
同一个业务主键空间在任意时刻只能有一个权威生成器,除非已经设计了明确不冲突的号段。
6.2 风险二:多库分片ID被误当单库AUTO_INCREMENT
比如:
db0 → 奇数 db1 → 偶数或者:
每个分片从不同亿级号段开始这些信息可能根本不在表 DDL 中,而在:
MySQL系统变量 部署配置 中间件 应用代码迁移清单必须覆盖数据库外部。
6.3 风险三:Sequence CACHE造成空洞被误判数据丢失
KingbaseES 官方文档说明,缓存序列号能提升性能,但实例异常关闭时,缓存中尚未使用的值可能被跳过。
这不是订单丢失。
真正的订单完整性应该通过:
业务唯一键 记录数 状态 消息链路判断。
不要用:
ID必须连续做数据完整性校验。
6.4 风险四:历史高值异常
可能绝大多数:
id < 4亿但曾经人工修复:
id=9,000,000,000如果只按“正常增长趋势”设置目标序列:
4亿+1最终仍会撞到历史记录。
所以必须读取真实:
MAX(id)6.5 风险五:BIGINT UNSIGNED容量问题
如果 MySQL 使用:
BIGINT UNSIGNED目标类型范围可能不同。
即使当前数据没超过范围,也要评估:
未来3年/5年增长不能等迁移后几年才发现主键逼近上限。
6.6 风险六:LAST_INSERT_ID依赖被遗漏
应用可能直接执行:
SELECTLAST_INSERT_ID();MySQL 官方说明LAST_INSERT_ID()对当前连接生成的 AUTO_INCREMENT 值有明确语义。
迁移后不能假设这个函数和连接态语义仍然完全一样。
更推荐应用通过目标驱动支持的:
generated keys RETURNING获取新主键,并做真实连接池测试。
6.7 风险七:Sequence权限遗漏
如果用:
Sequence + Default应用账号除了表 INSERT 权限,还需要确认能够正常访问相关序列。
这种权限问题往往在:
DBA账号测试成功 生产应用账号失败时才暴露。
切换清单要使用真实应用账号执行。
回退方案:回源之前也要重新校准AUTO_INCREMENT
假设:
MySQL最后ID = 386520100切到 KingbaseES 后又写了:
50000条最大 ID 已经:
386570100现在因为应用兼容问题需要回退 MySQL。
不能简单:
把连接串切回MySQL否则 MySQL 原计数器可能继续从:
386520101生成,而 KingbaseES 窗口期已经使用了这些 ID。
正确回退:
1. 停止KingbaseES新写 2. 固化目标最后ID和业务水位 3. 将目标新增记录反向同步MySQL并保留原ID 4. 校验两端MAX(id) 5. 将MySQL AUTO_INCREMENT推进到全局MAX(id)+安全步长 6. 恢复MySQL写入口 7. KingbaseES转为只读排障回退原则和切流原则其实完全一致:
下一任主库的主键生成器,必须位于所有已使用ID之后。
最终复盘
MySQL 到 KingbaseES 的 AUTO_INCREMENT 迁移建议按五层处理:
第一层:识别源编号架构 AUTO_INCREMENT / increment+offset / 分片 / 外部ID 第二层:选择目标生成器 Identity / Sequence / 外部ID服务 第三层:保留历史主键 全量 + CDC 都显式传原ID 第四层:校准生成器 NEXT VALUE > 全局MAX(id) 第五层:回归与回退 并发写 + 主键回传 + 双端水位 + 回源校准如果只记住一句话:
AUTO_INCREMENT 迁移不是把关键字换掉,而是在迁移“谁拥有下一个唯一ID的生成权”。
这个生成权只要在切换窗口中模糊一秒,就可能留下后续非常难修复的主键冲突。
附录 A:MySQL源DDL
CREATETABLEbiz_order(order_idBIGINTNOTNULLAUTO_INCREMENT,customer_idBIGINTNOTNULL,order_noVARCHAR(64)NOTNULL,amountDECIMAL(18,2)NOTNULL,PRIMARYKEY(order_id));附录 B:KingbaseES Identity方案
CREATETABLEbiz_order(order_idBIGINTGENERATEDBYDEFAULTASIDENTITY(STARTWITH1INCREMENTBY1)PRIMARYKEY,customer_idBIGINTNOTNULL,order_noVARCHAR(64)NOTNULL,amountNUMERIC(18,2)NOTNULL);附录 C:KingbaseES Sequence方案
CREATESEQUENCE biz_order_id_seqASBIGINTSTARTWITH386520101INCREMENTBY1CACHE100NOCYCLE;CREATETABLEbiz_order(order_idBIGINTPRIMARYKEYDEFAULTNEXTVAL('biz_order_id_seq'),...);附录 D:最低回归清单
[ ] AUTO_INCREMENT列已全部扫描 [ ] 当前MAX(id)已记录 [ ] increment/offset已记录 [ ] UNSIGNED范围已评估 [ ] 分库分表/外部ID逻辑已识别 [ ] 全量导入保留原ID [ ] CDC保留原ID [ ] 目标生成器已二次校准 [ ] 单条INSERT主键回传通过 [ ] 批量INSERT主键回传通过 [ ] 10/50/100并发无重复 [ ] 回滚后写入通过 [ ] 应用真实账号权限通过 [ ] 回退AUTO_INCREMENT校准脚本已演练转载自:https://blog.csdn.net/u014727709/article/details/163728579
欢迎 👍点赞✍评论⭐收藏,欢迎指正