接到这个需求的时候我脑子里第一个画面就是前两年做某数据平台项目时被几百张 MySQL 业务表的建表语句按在地上反复摩擦的场景。异构数据库之间的表结构迁移尤其是从 MySQL 到 Apache Doris远没有“复制粘贴然后改两个关键字”那么简单。MySQL 的索引定义、自增列、字符集、分区语法到了 Doris 里要么语义完全变了要么干脆不支持。手改一张表还好说几十上百张表轮下来纯手工干不光是体力活还特别容易出错。所以我当时就花了一个周末把一个“MySQL to Doris 创表语句转换脚本”给写了出来靠字符级解析把 Create Table 语句自动翻译成 Doris 能直接执行的版本。这篇就把它完整拆开讲一遍核心思路、类型映射规则、实操代码、踩过的坑一次性都给你。这个脚本解决的核心问题很简单批量地把 MySQL 的建表 DDL 转成 Doris 可执行的 DDL同时自动完成表模型选择、字段类型映射、索引剔除、分区分桶规则的初始生成。适合谁用正在做数据仓库选型、需要把业务库同步到 Doris 做分析查询的工程师或者被领导突然丢来一句“把这几百张表迁到数仓”的倒霉蛋。即便你只是单纯好奇 Doris 语法和 MySQL 到底差在哪这篇里的映射表和对比分析也有参考价值。1. 为什么需要 MySQL 转 Doris 的建表语句转换脚本1.1 MySQL 与 Doris 建表语法的本质差异先说一个最容易被忽略的事实Doris 虽然高度兼容 MySQL 协议甚至你用 Navicat 都能直接连上去执行 SQL但它骨子里是一个 MPP 分析型数据库不是 MySQL 的“增强版”。这句话决定了你在 MySQL 里写的建表语句在 Doris 里大概率跑不通或者能跑通但语义完全不对。MySQL 的建表语句核心是“字段 索引 存储引擎 字符集”。它关心的是怎么存、怎么快速检索索引是 B 树数据可以随便更新。Doris 则完全是另一套哲学它关心的是“怎么分布、怎么排序、怎么聚合”。每个表必须有分桶列数据按分桶列 hash 分布到不同的 BE 节点上每个表还有独立的排序键Sort Key决定了数据在节点内的物理存储顺序。所以你在 MySQL 里写的 PRIMARY KEY、UNIQUE KEY、INDEX在 Doris 里不能照搬它们会变成表模型的一部分甚至直接消失。拿我实际迁移过的一张订单表来举例MySQL 里它是这样写的CREATE TABLE order_info ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, user_id bigint(20) NOT NULL COMMENT 用户ID, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态, amount decimal(10,2) NOT NULL DEFAULT 0.00, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单信息表;这张表里至少四个点到了 Doris 是要被“改造”的AUTO_INCREMENTDoris 不支持自增列KEYidx_user_idDoris 的 Duplicate 模型下不需要普通二级索引ENGINEInnoDBDoris 不认识PRIMARY KEY在 Doris 的 Unique 模型里只能指定一个 UNIQUE KEY不能叫 PRIMARY KEY。这还没提 DEFAULT CHARSET、CURRENT_TIMESTAMP 这类 MySQL 特有表达式。手工改一张就要五分钟五十张就是两个半小时而且每张表的字段类型映射还不一样脑子根本记不过来。1.2 这个脚本能解决什么问题、适合谁来用这个脚本的设计目标很明确把人工三十分钟的工作量压缩到五秒以内同时把错误率降下来。它能做到的事情包括自动识别 MySQL DDL 里的表名、字段列表、索引声明、分区声明、表属性然后输出一份完整的 Doris 建表语句。字段类型自动映射比如bigint(20)去掉显示宽度变成bigintdatetime保留tinytext转varchar。自动剔除 Doris 不支持的语法比如AUTO_INCREMENT、ENGINEInnoDB、DEFAULT CHARSET、KEY索引。基于主键或唯一键自动选择 Doris 表模型根据业务字段预生成分桶键和分桶数。支持批量模式一个目录下几百个 DDL 文件一把梭。适合的使用场景有三类第一类是数仓项目初始化需要把几十上百张 MySQL 业务表的结构导入 Doris第二类是实时同步管道的表结构管理Flink CDC 同步数据时 Doris 端需要预先建好目标表不可能每张表都手工敲第三类是数据湖仓选型测试想快速评估 Doris 能不能覆盖现有业务表先用脚本把全量建表语句过一遍看看哪些表能平滑迁移、哪些需要改造。如果你只是零散地用 Doris 做几张报表那这个脚本确实有点大材小用。但一旦表数量超过五十张或者需要反复重建表结构它节省的时间就是可量化的。2. 转换规则设计与核心逻辑拆解2.1 表模型与排序键的自动判断逻辑转换脚本最核心的逻辑不在字符串替换而在于表模型的自动选择。Doris 有三种表模型Duplicate明细模型、Aggregate聚合模型、Unique主键唯一模型。选错模型的后果很严重最常见的是 Unique 模型选了 Aggregate导致同步数据时老数据更新后新旧版本叠加查询结果出现重复记录。反过来明细表被设成了 Aggregate数据一进来就自动聚合明细永远查不出来了。我的自动判断规则是这样设计的如果 DDL 里有PRIMARY KEY或者UNIQUE KEY优先判定为 Unique 模型。如果 DDL 里没有主键和唯一键判定为 Duplicate 模型。如果字段名里带明显聚合特征比如字段后缀是_sum、_count、_avg或者字段注释里有“累计值”“总额”之类的词且存在主键判定为 Aggregate 模型。当然这个判断是“基于常见实践的默认值”不一定 100% 符合业务语义。比如有些表虽然定义了主键但业务上其实需要保留全量历史明细这时 Unique 模型反而会覆盖旧数据。所以脚本默认用 Unique但输出 DDL 里会保留一份注释提醒你根据业务选择模型。脚本要做的不是替你做业务决策而是把决策成本和试错成本降到最低。排序键Sort Key在 Doris 里默认就是表的前几个字段。MySQL 的 PRIMARY KEY 刚好可以映射成 Unique 模型的 UNIQUE KEY而 UNIQUE KEY 列天然就是排序键不需要额外指定。这里有个需要注意的点Doris 的排序键一旦确定不建议随意更改它影响的是前缀查询效率和聚合性能。脚本默认取 UNIQUE KEY 的第一列作为分桶键如果你不想让分桶键和排序键耦合可以后续手工调整。2.2 字段类型映射表从 MySQL 到 Doris字段类型的映射是把 MySQL DDL 翻译成 Doris DDL 的基本功。这块我整理了一个映射表是脚本的核心词典也是手工操作时最需要背下来的东西。MySQL 类型Doris 类型转换说明TINYINT(4)TINYINT去掉显示宽度SMALLINT(6)SMALLINT去掉显示宽度INT(11)INT去掉显示宽度BIGINT(20)BIGINT去掉显示宽度DECIMAL(10,2)DECIMAL(10,2)原样保留但注意 Doris 压精度更高DECIMAL(10,2) 直接可用FLOATFLOAT原样保留DOUBLEDOUBLE原样保留CHAR(n)CHAR(n)注意 Doris 的 CHAR 也带长度VARCHAR(n)VARCHAR(n)n 最大 65533超出需转 TEXTTEXT / TINYTEXT / MEDIUMTEXT / LONGTEXTSTRINGDoris 的 STRING 等价于 MySQL 的 TEXT长度上限 2GBBLOB 系列STRING二进制数据建议用 STRING 存储Doris 不区分 BLOBDATEDATE原样保留DATETIMEDATETIME原样保留TIMESTAMPDATETIMEMySQL 的 TIMESTAMP 有 2038 年问题Doris 统一用 DATETIMEJSONJSONDoris 1.2 之后原生支持 JSON也可转 STRINGENUMVARCHAR(255)ENUM 在 Doris 识别不佳转 VARCHAR 更稳妥BIT(n)BOOLEAN 或 TINYINTBIT(1) 自动转 BOOLEANBIT(n) 转 TINYINT有个最容易翻车的点是varchar的长度。MySQL 里varchar(500)、varchar(1000)很常见但 Doris 单列 varchar 默认最大 65533 字节如果你的 MySQL 表里有超大 varchar转换时脚本会按超长规则自动升级为 STRING否则建表直接报错。反过来MySQL 的tinytext、mediumtext这类小体积文本类型Doris 里用 STRING 统一承接是完全可行的因为 Doris 的 STRING 底层存储更灵活没有 MySQL 那种页内大小限制。另外说下字符集与排序规则。MySQL 的 DDL 里几乎必带CHARSETutf8mb4和COLLATE但 Doris 建表语句里不需要写字符集它默认就是 UTF-8。脚本会把DEFAULT CHARSET、COLLATE之类的内容整段剥掉防止 Doris 解析报错。这里要特别提醒如果你的 MySQL 表字符集是 latin1 之类非 UTF-8 的脚本会输出一条警告日志因为数据同步时可能产生乱码这类表需要单独人工确认。2.3 索引、约束、自增的取舍策略MySQL 的二级索引KEY、INDEX和 Doris 的索引体系完全是两码事。Doris 的查询加速靠的是分区分桶裁剪 排序键前缀命中 物化视图 点查时开启的 Bloom Filter / 倒排索引它不支持 MySQL 那种通用 B 树二级索引。所以转换脚本的策略是直接丢弃全部KEY/INDEX声明不做任何翻译。这个策略背后有个取舍。如果你在 MySQL 里有大量基于非排序键的等值查询比如订单表按 user_id 查在 Doris 里如果不做任何处理这类查询效率确实不如 MySQL。但解决方案不是给 Doris 建二级索引而是要么把 user_id 加入排序键要么建物化视图要么开启 Bloom Filter。这是 Doris 的优化路径脚本没法代替你判断业务查询模式。主键和唯一键的处理就要精细多了。Doris 支持UNIQUE KEY (col1, col2)语法但它是“非强约束唯一”意思是数据导入时按该键去重而不是像 MySQL 那样插入重复直接报错。所以转换规则是如果主键是单列输出UNIQUE KEY (col_name)。如果主键是复合主键把多列合并进UNIQUE KEY (col1, col2, ...)。把主键中的 AUTO_INCREMENT 自增属性删除因为 Doris 不支持自增。AUTO_INCREMENT 的处理值得单独说。Doris 从 2.0 开始支持了部分自增能力但建表层面仍然不建议依赖它。OLAP 场景下统一的做法是用雪花算法或 UUID 在上游生成唯一 ID再同步到 Doris。脚本的默认策略是直接把AUTO_INCREMENT以及它的NOT NULL DEFAULT一起剥掉只保留类型声明。如果你确实需要自增列可以在转换后手动加上 Doris 支持的属性。2.4 分区分桶规则的生成策略Doris 建表语句里除了字段列表最重要的就是分区PARTITION和分桶DISTRIBUTED BY HASH两段语法了。MySQL 的表分区在 Doris 面前不太够看因为 Doris 支持的是真正的分布式分区数据可以按时间分区的粒度在不同 BE 节点间分布。脚本对分区部分采用“不强行翻译”的原则检查 MySQL DDL 里有没有PARTITION BY RANGE如果有提取分区字段输出到 Doris 的同名字段并且额外补充动态分区的推荐配置。但实际执行时如果 MySQL 的分区字段是整型比如按月份分区用的 yyyymm 整数Doris 的 RANGE 分区需要显式定义各个分区的边界这里脚本只生成模板不自动穷举分区。因为自动穷举分区边界需要知道业务数据范围这是运行时信息DDL 转换脚本不该瞎猜。分桶才是真正自动生成的部分。分桶列的选择直接决定数据分布是否均匀。脚本的优先顺序是如果 MySQL 表有主键取主键的第一列作为分桶列。如果主键列不是数值型且区分度差比如性别列只有两个值则取后面第一个数值型高基数字段作为分桶列。如果表没有主键取第一个字段作为分桶列并输出警告。分桶数的默认值是 10。为什么是 10经验法则是一张表的分桶数建议取 BE 节点数量的 2~3 倍同时单个分桶的数据量控制在 1GB 左右。如果你有 3 个 BE 节点10 个分桶是合理下限。如果节点更多或数据量更大建议按分桶数 max(节点数 * 3, 预估数据量 / 1GB)调整。脚本生成的分桶数只是起点数据量上来后建议用 Doris 的ALTER TABLE动态调整分桶数量。3. 脚本实现与完整实操过程3.1 技术选型为什么用文本解析而不是数据库连接抽取写这个脚本之前我纠结过一个问题到底是通过 JDBC / pymysql 直连 MySQL读 information_schema 拿元数据生成 DDL还是直接对已有的 DDL 文件做文本解析两条路线的利弊我当时列了个表技术路线优点缺点连接 MySQL 读取元数据能拿到精确到每个列的注释、默认值需要账号权限依赖目标库在线不方便离线处理解析 DDL 文件灵活、离线可用、可批量处理要求 DDL 语法规范需要考虑各种异常写法我最终选择了文本解析路径。原因有三第一很多场景下我们手上只有 DDL 文件比如从云数据库导出的 SQL dump或者版本管理仓库里的 schema 变更记录这时候根本没有在线 MySQL 可用。第二文本解析是纯函数式逻辑输入输出确定方便写单元测试后续往其他数据库比如 PostgreSQL 到 Doris扩展也更顺手。第三我在生产环境踩过连接抽取的坑有些数据库账号只有 SELECT 权限查 information_schema 时视图不全拿到的东西反而不如 DDL 文件完整。文本解析用到的核心库就两个Python 标准库re负责正则提取typing负责类型提示。整个脚本刻意不引入第三方依赖这样在任何一台机器上都能直接跑不用折腾虚拟环境。3.2 脚本主流程与关键代码实现脚本的整体流程是“四段式”先取表名再拆字段区然后提取键信息最后拼 Doris DDL。第一步提取表名。MySQL DDL 的表名可能带反引号也可能不带脚本统一剥掉反引号。import re def extract_table_name(ddl): pattern re.compile(rCREATE\sTABLE\s(?:IF\sNOT\sEXISTS\s)??(\w)?, re.IGNORECASE) match pattern.search(ddl) if not match: raise ValueError(无法从DDL中提取表名) return match.group(1)第二步是拆分字段区。这一步的关键不是全文正则匹配而是按“括号配平”的思想提取括号内内容。因为 MySQL DDL 的字段定义和索引定义都在最外层括号里而且 содержимое里面还嵌套着KEY定义。比较稳妥的做法是扫描整个 DDL 字符串记录最外层括号的起止位置。def extract_columns_and_keys(ddl): outer_paren_start ddl.find(() outer_paren_end ddl.rfind()) inner ddl[outer_paren_start 1:outer_paren_end] # 分行解析字段行一般以逗号分隔 lines re.split(r,\n|,\r\n, inner) field_lines [] key_lines [] for line in lines: stripped line.strip() if re.match(r^(PRIMARY|UNIQUE|KEY|INDEX|CONSTRAINT|FULLTEXT), stripped, re.IGNORECASE): key_lines.append(stripped) else: field_lines.append(stripped) return field_lines, key_lines第三步是字段类型映射。这是脚本的核心变换逻辑我直接用一个字典 正则兜底来做。对于bigint(20)、varchar(255)这类带括号的类型需要先提取出类型名和长度参数再分别处理TYPE_MAP { tinyint: tinyint, smallint: smallint, mediumint: int, int: int, integer: int, bigint: bigint, decimal: decimal, numeric: decimal, float: float, double: double, char: char, varchar: varchar, text: string, tinytext: string, mediumtext: string, longtext: string, blob: string, date: date, datetime: datetime, timestamp: datetime, json: json, bit: tinyint, } def map_field_type(type_str): # type_str 形如 varchar(255)、bigint(20) base re.sub(r\(.*\), , type_str).lower() # 先查主映射表 mapped TYPE_MAP.get(base, string) # varchar 长度高于 65533 自动转 STRING if base varchar: m re.search(r\((\d)\), type_str) if m and int(m.group(1)) 65533: return string return mapped第四步是拼接 Doris DDL。这里要处理三个小细节去 AUTO_INCREMENT、去 DEFAULT CURRENT_TIMESTAMPDoris 原生不支持这个默认值表达式、去 COLLATE。Doris 建表语句的拼装模板如下def build_doris_ddl(table_name, field_defs, unique_key_cols, bucket_col, bucket_num, table_comment): unique_key_str , .join(unique_key_cols) fields_str ,\n .join(field_defs) ddl fCREATE TABLE IF NOT EXISTS {table_name} ( {fields_str}, UNIQUE KEY ({unique_key_str}) ) COMMENT {table_comment or } DISTRIBUTED BY HASH({bucket_col}) BUCKETS {bucket_num} PROPERTIES ( replication_num 1 ); return ddl这里replication_num 1是开发/测试环境的默认配置。生产环境至少要设为 3否则 BE 节点挂掉一个整个表的数据就会不可用。脚本把这个值作为参数暴露不硬编码。说实话我这个脚本并不依赖某个单一的“AI 生成”步骤它的核心资产是那张类型映射表和决策树。你可能觉得代码很简单确实简单。正则解析的 DDL 引擎本来就不该复杂真正复杂的部分是前面 2.2 和 2.3 里那些业务决策逻辑代码只是把这些逻辑变成了可执行的东西。3.3 完整案例演示一张 MySQL 订单表的转换全过程光讲规则不给案例等于耍流氓。我们拿开头那张order_info表走一遍完整转换流程。输入 DDLCREATE TABLE order_info ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, user_id bigint(20) NOT NULL COMMENT 用户ID, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 状态, amount decimal(10,2) NOT NULL DEFAULT 0.00, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单信息表;脚本执行后输出的 Doris DDLCREATE TABLE IF NOT EXISTS order_info ( id bigint NOT NULL COMMENT 订单号, order_no varchar(32) NOT NULL COMMENT 订单号, user_id bigint NOT NULL COMMENT 用户ID, status tinyint NOT NULL DEFAULT 0 COMMENT 状态, amount decimal(10,2) NOT NULL DEFAULT 0.00, create_time datetime NOT NULL COMMENT 创建时间, UNIQUE KEY (id) ) COMMENT 订单信息表 DISTRIBUTED BY HASH(id) BUCKETS 10 PROPERTIES ( replication_num 1 );对比看下改动点bigint(20)变成了bigint显示宽度在 Doris 没有意义。AUTO_INCREMENT被去掉。DEFAULT CURRENT_TIMESTAMP被去掉因为 Doris 2.0 版本之前默认值只能写常量。如果你确实需要自动填充创建时间建议在上游同步时用 NOW() 函数写入或者给 Doris 表新增一个 DATETIME 列并在同步管道里赋值。PRIMARY KEY变成了UNIQUE KEY。KEY idx_user_id被丢弃。ENGINEInnoDB DEFAULT CHARSETutf8mb4整段删除替换成 PROPERTIES。这里你可能会问create_time的注释“创建时间”哪来的其实原 DDL 里create_time没有显式注释我这里是随手补的。真实的字段注释提取逻辑是匹配COMMENT xxx片段拿到后统一做成双引号字符串拼到 Doris DDL 里。Doris 的字段注释有个特殊点字段注释里的单引号必须转义否则建表语句会报错。比如一个字段注释是its a test转换脚本会把单引号转成双引号包裹再对内部的双引号做转义。这块容易踩坑测试时我专门写了一个异常注释的用例。3.4 批量转换与自动化集成方案单表转换能跑通之后批量处理就水到渠成了。脚本支持三种输入模式单文件模式python mysql2doris.py -f ./schema/order_info.sql目录模式python mysql2doris.py -d ./schema/ -o ./output/管道模式cat order.sql | python mysql2doris.py -s目录模式我在项目里用得最多几百张表的 DDL 放到一个 schema 目录下脚本逐个解析生成的 DDL 写入输出目录文件名用doris_前缀 原表名。所有解析失败的 SQL 汇总到一个failed.json文件里里面记录了表名和失败原因方便集中排查。批量处理时有个工程细节需要注意DDL 文件的编码格式。UTF-8 无 BOM 是最标准的情况但 Windows 环境下导出的 SQL 经常是 GBK 编码或者 UTF-8 with BOM。脚本打开文件时做两次尝试先 utf-8 后 gbk都不行就直接跳过并记录错误。这在团队协作场景下非常实用因为很多人根本意识不到自己导出的文件是什么编码。如果要接入 CI/CD 流程可以把脚本封装成一个命令行工具放进 Docker 镜像在代码仓库的 CI 阶段对新增的 MySQL DDL 自动生成 Doris DDL做语法校验通过调用 Doris 的EXPLAIN或直接试跑建表后再提交。这样表结构变更的同步就实现了流程化不再依赖某个人手工记忆转换规则。4. 踩坑记录与常见问题速查4.1 六个最容易翻车的地方第一个坑主键字段里有默认值。MySQL 的主键一般不会有默认值但复合主键里的某个字段偶尔会带DEFAULT 0。转换脚本拿到这种字段后保留了 DEFAULT 子句然后在 Doris 里建表报错。因为 Doris 的 UNIQUE KEY 列不允许带默认值。修复方案是脚本里做一个检查只要是出现在 UNIQUE KEY 列表里的字段强制去掉 DEFAULT 部分。第二个坑DECIMAL 精度溢出。MySQL 允许decimal(65,10)这种变态精度但 Doris 的 DECIMAL 最大精度是 38 位。转换脚如果直接把精度透传过去Doris 直接报错。处理方式是把精度大于 38 的 decimal 转成 STRING并在输出日志里打警告。实际业务里用超过 38 位精度的场景几乎为零万一遇到大概率是建模时对金额字段的精度定义不合理。第三个坑非空约束与排序键的冲突。Doris 的排序键列默认不允许 NULL。MySQL 的 PRIMARY KEY 列本身就是 NOT NULL所以这个冲突只在普通索引列上出现不影响转换结果。但如果你手工把某个 MySQL 可空列设为分桶键Doris 会报错。脚本生成分桶键时会检查该字段是否带NOT NULL没有的话自动补上。第四个坑表注释里的特殊字符。MySQL DDL 的 COMMENT 用单引号包裹注释里如果本身有单引号MySQL 会用\转义。转换脚本读取时如果直接按正则匹配COMMENT\s(.?)遇到转义单引号就会截断。需要写一个更加健壮的捕获逻辑匹配到 COMMENT 后从后往前找单引号作为结束边界。第五个坑分区字段名字带反引号。MySQL DDL 里分区字段写成create_time脚本提取分区字段时如果忘了剥反引号生成的 Doris DDL 里就会出现create_time和create_time不一致的问题导致建分桶时报字段不存在。第六个坑同一张表出现多个 UNIQUE KEY。MySQL 允许一张表有多个唯一索引但 Doris 的 Unique 模型只能有一个 UNIQUE KEY。脚本的默认策略是合并所有唯一键列去重后组合成一个复合 UNIQUE KEY。但这会改变去重粒度比如 MySQL 表有UNIQUE(order_no)和UNIQUE(user_id, status)如果合并成UNIQUE KEY (order_no, user_id, status)去重基数就变了。遇到这种情况脚本输出警告需要人工介入判断。4.2 常见错误对照速查表报错信息根因解决方案Unknown column xxx in field list分区/分桶字段名带反引号未处理干净或字段名大小写不一致检查脚本提取的字段列表统一字段名格式Duplicate column name xxxMySQL DDL 字段列表里普通字段和 KEY 字段重复需要在提取字段时过滤掉出现在 KEY 里的列名errCode 2, detailMessage key column size should be at most 3复合 UNIQUE KEY 的列数太多Doris 默认限制三列减少 UNIQUE KEY 列数或调整 FE 配置项max_key_column_numVarchar should have sizeVARCHAR 类型没带长度脚本对 varchar 的提取逻辑做兜底默认补 255Unsupported default valueDEFAULT 表达式 Doris 不支持去掉 DEFAULT 子句或改成常量Key column should not have default valueUNIQUE KEY 列带了 DEFAULT脚本强制去掉该列的 DEFAULT这些错误里前三个是我在真实测试时遇到的后面几个是同事用脚本时踩到的。有一个共性规律是报错信息看着五花八门但追根溯源问题基本都出在脚本解析 MySQL DDL 时某个边界情况没处理干净。这也侧面说明了 DDL 解析类工具的意义SQL 语法比想象中更复杂边界情况非常多人工处理时靠眼力发现不了但数据库解析器很诚实错它就是真报错。4.3 排查思路与自测方法拿到一个报错别一上来就看代码先做“最小化对比法”。就是拿同一张 MySQL 表手动写一份你认为正确的 Doris DDL然后跟脚本生成的 DDL 逐行 diff。绝大多数情况下报错位置就是 diff 里多出来的那一行。这个方法说起来简单但能少走很多弯路。自测方面我强烈建议给转换脚本配一套 golden file 自动化测试。准备一批覆盖各种边界的输入 DDL 用例比如复合主键、无主键、大 varchar、TEXT 类型、带分区、带特殊注释、空表定义等等。每个用例配一个期望输出的 Doris DDL 文件。每次改完代码跑一遍测试套件看看哪些输出变了。这样你就不太可能改一处逻辑、把另外八个表的转换结果全干崩了。我写的这个脚本里测试用例数量超过六十个大部分是从生产环境的几百张表里抽取的真实 DDL。排查时还可以利用 Doris 自身的语法校验能力。批量转换生成完 DDL 后不要直接全部执行先挑十张表试建确认语法通过后再全量执行。Doris 建表语句的执行很快秒级返回我们当时做了一个小工具把所有生成的 DDL 通过 JDBC 批量提交到 Doris收集报错信息后回传形成一个“转换-校验-修复”的闭环。5. 脚本之外写给接手 Doris 的同学的建议5.1 转换完成后必须手工确认的配置项脚本能帮你省掉大量机械劳动但它输出的是“初始可用版本”不是“生产最优版本”。有几项配置建议建表前根据业务特性做一次确认。第一项是replication_num。我脚本默认给的是 1这是为了方便开发环境快速验证。生产环境最少三副本否则任何一个 BE 节点异常数据就面临着丢失风险。修改方式是在 Doris 集群创建后执行ALTER TABLE ... SET (replication_num 3)或者建表时直接改参数。第二项是分桶数。10 个分桶对小表百万行级别没毛病但如果你的表有数亿行10 个桶会导致单个桶数据量过大查询时的并行能力受限。建议按前面说的分桶数 BE节点数 * 3 ~ 10的区间调整。数据量是动态增长的初期少分桶后续数据涨上来再用ALTER TABLE ... DISTRIBUTED BY HASH(...) BUCKETS n扩容。Doris 本身也支持自动分桶建表时不写 BUCKETS由系统根据机器配置自动决定新版本建议直接用 autobucket 特性。第三项是动态分区。MySQL 表如果按天增量同步Doris 建表时最好加上动态分区配置让系统自动创建未来 N 天的分区避免每天凌晨都要手工ADD PARTITION。我脚本输出的 DDL 没有默认加这个但保留了注释提示需要根据表的日期字段手动开启。第四项是字段注释里包含的同步语义。比如你需要知道哪些字段是从上游系统直接透传的哪些是数仓加工生成的。Doris 的 COMMENT 能承载这些信息建表时值得认真填写。脚本生成的 COMMENT 大多数来自 MySQL 原表如果原表本身注释写得不规范这会传导到 Doris影响后续元数据管理。5.2 从“能用”到“好用”的扩展方向这个脚本可以继续演进的方向我列一下自己想到的第一个方向是增加对ALTER TABLE语句的转换支持。现在只处理CREATE TABLE但实际项目里表结构经常变更需要把 MySQL 的ALTER TABLE ADD COLUMN转换成 Doris 的对应语法。这个难度比 CREATE 小但需要处理列位置AFTER xxx的问题因为 Doris 的 ADD COLUMN 语法不支持 AFTER新增列统一排在最后。第二个方向是增加类型推断的反馈机制。如果自动判断的 Unique / Duplicate 模型不符合业务预期应该能在输出 DDL 的注释里看到提示以及一段解释文字。也就是说脚本除了生成 DDL还可以顺手产出一份“迁移说明文档”记录每张表的模型选择理由、分桶键选择理由、可能存在的风险点。当时我手写这些说明简直写到崩溃如果脚本能自动生成初稿能省不少力气。第三个方向是集成到同步工具链里。比如配合 Flink CDC在启动同步任务前自动从 MySQL 拉取 DDL、转换、执行到 Doris实现表结构的自动同步。现在这类能力有一些商业工具内置了但自研脚本在可控性和私有化部署场景下仍然有价值。我自己的体会是这类转换脚本永远做不到 100% 完美它的定位是帮你把常态化的、机械的、容易出错的 80% 工作量自动化掉剩下 20% 需要结合业务语义做判断的部分才考验你对两个数据库的理解。用脚本不是为了消灭人工而是为了把人工精力集中在真正有价值的地方——比如设计 Doris 表的模型、确定分桶策略、规划分区生命周期。工具本身不难写难的是你想清楚每一行转换背后的“为什么”这也是写这篇长文想传达的核心。
阅读完成 · 觉得有帮助?