【避坑指南】从 MySQL 到 Apache Doris:自增主键的“隐藏陷阱”与数据迁移最佳实践
2026/7/18 19:55:54 网站建设 项目流程

前言

在从传统关系型数据库(如 MySQL)向现代分布式分析型数据库Apache Doris迁移的过程中,很多开发者会习惯性地套用过去的经验。

最近在处理一个数据迁移场景时,遇到了一个非常经典且隐蔽的“坑”:如何将一批“带 ID 的历史数据”和一批“不带 ID 的新增数据”合并导入到 Doris 中,并保证 ID 连续且不冲突?

如果直接套用 MySQL 的自增主键逻辑,在 Doris 中会遭遇严重的数据覆盖或 ID 冲突。今天,我们就来深度扒一扒 Doris 自增列的底层逻辑,并给出最优雅、高效的解决方案。


一、 还原现场:MySQL 习惯在 Doris 中翻车了?

1. 我们的预期(MySQL 模式)

假设我们有两批数据需要导入到目标表:

  • 第一批数据:已经有现成的id(1 到 1000)。
  • 第二批数据:没有id,希望导入时自动从1001开始自增。

MySQL中,我们的标准操作是:

  1. 创建带自增主键的表。
  2. 显式插入第一批带id的数据。
  3. 插入第二批不带id的数据。

MySQL 在执行第 2 步时,会自动将内部的自增计数器更新为1001(即max_id + 1),因此第 3 步绝对不会发生冲突。

2. Doris 中的惨烈翻车

但在Apache Doris中,如果你执行同样的操作,就会发生灾难:

  • 现象:当你导入第二批不带id的数据时,Doris 依然会从1(或者它内部缓存的某个小数值)开始自增分配 ID。
  • 后果
    • 如果你的表是Unique 模型,新写入的数据会直接覆盖掉你第一批导入的历史数据!
    • 如果你的表是Duplicate 模型,表中会出现大量重复的 ID,导致主键失去唯一性。

二、 深度解析:为什么 Doris 会“失控”?

参考:doris官方描述

要理解这个问题,我们需要看一眼 Doris 的分布式架构。

Doris 是一个分布式 MPP 数据库。为了在高并发、海量数据写入时保证高性能,Doris 的自增列(Auto-Increment)采用了**“批量预分配(Cache)”**的机制:

  1. FE 节点管理与缓存:FE 负责管理自增 ID,为了避免每次写入都去申请 ID 导致性能瓶颈,FE 会一次性向 BE 节点分发一个 ID 缓存区间(例如 1~10000)。
  2. 手动写入不触发更新:当你通过INSERT语句显式指定 ID插入数据时,Doris 的底层存储引擎会直接写入该值,但它绝对不会去通知 FE 的自增计数器去更新它的 Cache 指针
  3. 无法手动修改起点:Doris 目前不支持像 MySQL 那样通过ALTER TABLE tbl AUTO_INCREMENT = x来手动指定自增起点。

因此,手动插入的 ID 和系统自动生成的 ID,在 Doris 里是完全脱节的。


三、 破局之法:动态窗口函数法(Best Practice)

既然 Doris 的AUTO_INCREMENT属性在混合导入场景下有这个限制,我们该如何解决?

最佳实践:放弃AUTO_INCREMENT属性,利用 Doris 强大的 MPP 计算能力,在导入时通过窗口函数动态生成 ID。

这个方案不仅安全,而且省去了创建临时表、合并表的繁琐步骤,一步到位。

实操步骤

步骤 1:创建普通目标表(不带自增属性)

id定义为普通的BIGINT类型。

CREATETABLEtarget_table(idBIGINTCOMMENT"唯一ID",nameVARCHAR(50)COMMENT"姓名",create_timeDATETIMECOMMENT"创建时间")ENGINE=OLAPDUPLICATEKEY(id)DISTRIBUTEDBYHASH(id)BUCKETS10;
步骤 2:导入第一批历史数据(带 ID)

直接将带有id的数据写入目标表。

INSERTINTOtarget_table(id,name,create_time)SELECTid,name,create_timeFROMsource_batch_1;
步骤 3:获取当前最大 ID

在导入第二批数据前,查询一次当前表中的最大 ID。假设查询结果为9999

SELECTMAX(id)FROMtarget_table;
步骤 4:利用窗口函数,动态计算并导入第二批数据

在导入第二批不带 ID 的数据时,利用ROW_NUMBER() OVER()窗口函数,在最大 ID(9999)的基础上进行累加。

INSERTINTOtarget_table(id,name,create_time)SELECT9999+ROW_NUMBER()OVER()ASid,-- 在最大ID基础上动态累加name,now()ascreate_timeFROMsource_batch_2;

四、 方案优势总结

相比于“新建表1、新建表2、再合并”的传统套路,这个方案有以下压倒性优势:

  1. 绝对安全,零冲突:完全不依赖 Doris 内部的自增生成器,ID 连续性完全由 SQL 逻辑控制。
  2. 极速迁移,无 I/O 浪费:Doris 的 MPP 架构对ROW_NUMBER()这种窗口函数做了极高并发的优化,百万级数据瞬间即可完成 ID 计算并写入,无需产生临时表落盘。
  3. 极简运维:全程只需操作一张目标表,降低了迁移脚本的复杂度,非常适合自动化 ETL 任务。

五、 结语

Doris 作为优秀的现代实时数仓,在很多设计哲学上与传统的 MySQL 有着巨大差异。我们在享受其高并发、大吞吐的分析能力的同时,也要注意避开这些“分布式特性”带来的小坑。

希望这篇避坑指南能帮到正在做数据迁移的你!如果你有更好的方案,欢迎在评论区留言交流!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询