资讯动态

MySQL → Doris 通过 External Catalog 全量迁移完整落地方案

发布时间:2026/10/16 12:33:40 来源:尧图企业网站定制
适用场景Doris FE 集群通过mysqlcatalog 映射源端 MySQL 库脚本自动生成INSERT INTO ... SELECT全量同步 SQL一次性迁移整批表。 本次踩坑全部整理进【注意事项 / 坑点】章节后续部署直接对照执行。一、环境信息Doris FE 集群node01 (10.10.6.27)、node02 (10.10.6.28)、node03 (10.10.6.29)Master FE10.10.6.29:9010MySQL 源库mysql 8.0.26Catalog 名称mysql_src目标 Doris 库DWD迁移脚本gen_doris_sync.shbash 脚本自动读取 catalog 源表结构生成 INSERT 全量同步 SQL表清单文件table_list.txt二、前置准备必须全部做完否则后续报错2.1 网络 端口连通检查所有 FE/BE 节点Doris 所有 FE、BE 节点telnet 连通 MySQL 源库 3306 端口# 在node01/node02/node03全部执行 telnet mysql源IP 3306FE 节点内部互通9010、8030 端口互通防火墙放行。# FE之间互测 telnet 10.10.6.27 9010 telnet 10.10.6.28 9010 telnet 10.10.6.29 9010坑点节点之间端口不通FE 无法组成集群catalog 查询会超时失败。2.2 Doris 创建 MySQL Catalog外部目录登录 Doris MySQL 客户端执行创建映射 MySQL 的 catalogCREATE CATALOG mysql_src PROPERTIES ( typejdbc, usermysql源账号, passwordmysql源账号, jdbc_url jdbc:mysql://10.10.6.27:3306?useSSLfalserewriteBatchedStatementstrue, driver_url file:///usr/local/doris/apache-doris-4.1.4.1/fe/lib/mysql-connector-java-8.0.26.jar, driver_class com.mysql.cj.jdbc.Driver );校验 catalog 是否可用SWITCH mysql_src; SHOW DATABASES; DESC mysql_src.gz_wzy.t_reports_zxjb; -- 随便一张源表验证可以读取表结构坑点 1MySQL 账号权限不足需要给账号对应库的 SELECT 权限。 坑点 2MySQL8.0 默认 caching_sha2_passwordDoris 不支持源库账号改成 mysql_native_password。 坑点 3catalog 只是映射不自动建 Doris 目标表目标 Doris 表必须提前建好。2.3 准备 Doris 目标库CREATE DATABASE IF NOT EXISTS DWD;2.4 准备表清单 table_list.txt文件格式catalog.源库.源表名,Doris目标库.目标表名,分片主键,haspk/nopkhaspk有主键按主键分片批量导入每次 50000 条nopk无主键一次性全表导入 示例内容mysql_src.gz_wzy.t_reports_zxjb,DWD.wzy_t_reports_zxjb,id,haspk mysql_src.gz_wzy.t_rate_result,DWD.wzy_t_rate_result,,nopk坑点每行不能有多余空格逗号是英文逗号不能中文逗号。2.5 部署迁移脚本 gen_doris_sync.shvi gen_doris_sync.sh粘贴下面修复 bash 反引号 bug 后的最终脚本#!/bin/bash # gen_doris_sync.sh 生成Doris批量导入SQLmysql catalog迁移自动处理Doris关键字字段转义 # table_list.txt 格式mysql_src.源库.源表,Doris库.目标表,主键,haspk/nopk set -euo pipefail ########################【配置区按需修改】######################## OUTPUT_SQLmigrate_all.sql SPLIT_STEP50000 EXTRA_COLSdt|CURDATE(),sync_time|NOW(),data_source|mysql_gz # Doris关键字黑名单自动加反引号 RESERVED_WORDS(explain desc status order group table column values key char when then case end using primary unique) ################################################################### ${OUTPUT_SQL} # 关键字判断转义函数修复bash反引号命令替换坑 escape_col() { local col$1 local lower_col${col,,} for kw in ${RESERVED_WORDS[]};do if [[ ${lower_col} ${kw} ]];then echo \${col}\ return fi done echo ${col} } TABLE_LISTtable_list.txt if [[ ! -f ${TABLE_LIST} ]];then echo ERROR: 缺少table_list.txt exit 1 fi while IFS read -r line;do [[ -z ${line} || ${line} ~ ^# ]] continue echo 处理: ${line} IFS, read -r src_full dst_full pk_col pk_type ${line} # 读取源表所有字段 RAW_COL$(mysql -h127.0.0.1 -P9030 -uroot -proot20160118 -N -s -e DESC ${src_full};) col_list while IFS$\t read -r cname ctype rest;do [[ -z ${cname} ]] continue esc_c$(escape_col ${cname}) col_list${col_list}${esc_c}, done ${RAW_COL} col_list${col_list%,} # 处理附加字段 dt/sync_time/data_source extra_insert extra_select IFS, read -ra extra_arr ${EXTRA_COLS} for item in ${extra_arr[]};do IFS| read -r cname cexpr ${item} extra_insert${extra_insert}${cname}, extra_select${extra_select}${cexpr}, done extra_insert${extra_insert%,} extra_select${extra_select%,} full_insert_cols${col_list},${extra_insert} full_select_cols${col_list},${extra_select} echo -- ${src_full} 同步开始 ${OUTPUT_SQL} if [[ ${pk_type} haspk ]];then # 分片模式循环按主键分批 minmax$(mysql -h127.0.0.1 -P9030 -uroot -proot20160118 -N -s -e SELECT MIN(${pk_col}),MAX(${pk_col}) FROM ${src_full};) min_id$(echo ${minmax}|awk {print $1}) max_id$(echo ${minmax}|awk {print $2}) if [[ ${min_id} NULL || -z ${min_id} ]];then echo -- 表${src_full}无数据跳过 ${OUTPUT_SQL} continue fi start${min_id} while (( start max_id ));do end$((start SPLIT_STEP)) echo -- ${src_full} 分片: ${start} ~ ${end} ${OUTPUT_SQL} echo INSERT INTO ${dst_full} (${full_insert_cols}) ${OUTPUT_SQL} echo SELECT ${full_select_cols} ${OUTPUT_SQL} echo FROM ${src_full} ${OUTPUT_SQL} echo WHERE ${pk_col} ${start} AND ${pk_col} ${end}; ${OUTPUT_SQL} echo SELECT SLEEP(1); ${OUTPUT_SQL} start${end} done else # nopk 全表一次性导入 echo -- ${src_full} 无主键全量导入 ${OUTPUT_SQL} echo INSERT INTO ${dst_full} (${full_insert_cols}) ${OUTPUT_SQL} echo SELECT ${full_select_cols} ${OUTPUT_SQL} echo FROM ${src_full}; ${OUTPUT_SQL} echo SELECT SLEEP(1); ${OUTPUT_SQL} fi done ${TABLE_LIST} echo 脚本执行完成生成文件${OUTPUT_SQL} echo 校验命令grep -n \explain\ ${OUTPUT_SQL}赋权chmod x gen_doris_sync.sh三、执行生成 SQL 脚本./gen_doris_sync.sh✅ 成功输出migrate_all.sql校验关键字段转义grep explain migrate_all.sql坑点bash 原生反引号会被识别成命令执行旧脚本会报explain:未找到命令上面最终脚本已经修复。四、导入前强校验非常重要避免导入中途中断4.1 逐条核对 Doris 目标表结构核心原则Doris 目标表字段 MySQL 源表全部字段字段名、类型必须一一对应查看 catalog 源表结构DESC mysql_src.gz_wzy.t_reports_zxjb;查看 Doris 目标表DESC DWD.wzy_t_reports_zxjb;踩坑重点脚本自动读取源表所有字段Doris 目标表少任何一列导入直接报错中断本次多次遇到column xxx not found 中文列、Doris 关键字字段explain/status/desc必须在建表 / ALTER 时使用反引号包裹列名。示例补缺失字段语句ALTER TABLE DWD.wzy_t_reports_zxjb ADD COLUMN item INT COMMENT 风险顺序级别; ALTER TABLE DWD.wzy_t_rate_result ADD COLUMN 疲乏 VARCHAR(255);4.2 预单条测试推荐提前发现问题随便挑一张表单独执行一条 insert验证无报错INSERT INTO DWD.wzy_t_reports_zxjb (id,serviceRecordId,name,value,type,phone,create_time,item,dt,sync_time,data_source) SELECT id,serviceRecordId,name,value,type,phone,create_time,item,CURDATE(),NOW(),mysql_gz FROM mysql_src.gz_wzy.t_reports_zxjb WHERE id 1 AND id 50001 limit 1;五、后台执行全量导入方式 1严格串行遇到错误立刻停止适合初次校验nohup /usr/local/mysql-8.0.26/bin/mysql -h127.0.0.1 -P9030 -uroot -proot20160118 migrate_all.sql migrate_run.log 21方式 2容错模式单表报错跳过继续跑剩下表适合大批量迁移增加--force参数一张表报错不会终止整体任务末尾加直接后台跑nohup /usr/local/mysql-8.0.26/bin/mysql --force -h127.0.0.1 -P9030 -uroot -proot20160118 migrate_all.sql migrate_run.log 21 日志监控命令# 实时跟踪日志 tail -f migrate_run.log # 查看是否还有mysql导入进程 ps -ef | grep mysql坑点不加--force任意一张表报错整个导入任务直接终止后面所有表不会同步。六、迁移完成后校验步骤检查日志末尾确认无 ERRORgrep -i error migrate_run.log核对每张表行数对比 MySQL 源表与 Doris 目标表 count-- MySQL源 SELECT COUNT(*) FROM mysql_src.gz_wzy.t_reports_zxjb; -- Doris目标 SELECT COUNT(*) FROM DWD.wzy_t_reports_zxjb;抽样核对数据取少量行对比字段内容。七、全部踩坑汇总重点注意事项每次迁移必看Doris 目标表字段必须和 MySQL 源表完全对齐脚本自动 DESC 读取源表全部字段源表有什么字段INSERT 语句就带什么字段。Doris 少字段直接报column xxx not found导入中断。中文列名、关键字列名必须用xxx。bash 脚本反引号坑bash 中默认是命令替换早期脚本直接写explainbash 会尝试执行 explain 命令抛出explain:未找到命令。最终脚本使用\转义反引号修复该问题。Doris FE 集群节点 priority_networks 配置多 FE 集群每个节点priority_networks要指定本机 IP/32避免 FE 识别网卡 IP 错误helper 启动失败。priority_networks10.10.6.27/32坑用 / 24 会识别错误 IPFE 无法互相通信无法组成集群catalog 查询异常。FE 启动参数区分--helper只用于 FE 节点第一次加入集群节点已经加入集群后正常启停直接./bin/start_fe.sh --daemon不要再加 --helper 参数重复使用 helper 会元数据异常。FE 元数据损坏问题 更换 IP / 集群重组fe 元数据目录 doris-meta 下 bdb/image 目录残留旧元数据会导致 FE 启动失败。新增 FE 节点加入集群前清空 doris-meta 目录。MySQL Catalog 账号权限 MySQL 源账号必须拥有对应库的 SELECT 权限MySQL8.0 认证插件 caching_sha2_password 不被 Doris 支持源账号修改认证方式为 mysql_native_password。导入串行执行特性 原生 mysql 客户端导入 SQL 文件默认串行执行一条失败整体终止大批量迁移建议加上--force但导入结束后必须人工核对表行数确认失败表单独补数据。网络连通性检查范围FE 和 BE 都要能访问 MySQL 源库 3306不只是 FE 节点BE 执行查询拉取源数据BE 不通会查询超时。表清单 table_list.txt 格式必须英文逗号不能多余空格注释行用 #开头。SLEEP (1) 作用分片之间停顿 1 秒降低 Doris 写入压力防止 BE 负载过高不影响数据正确性。八、后续复用操作极简清单下次迁移直接执行确认所有 FE/BE 能访问 mysql 源 3306 端口检查 mysql_src catalog 正常可用在 Doris 建好目标库建好所有目标表字段和源表一一对应编辑 table_list.txt 添加迁移表清单./gen_doris_sync.sh生成 migrate_all.sql单条 SQL 预测试确认无报错nohup 后台执行导入tail 监控日志迁移完成核对各表 count 行数九、增量同步方案catalog 方式做定时增量或者一键检查表结构差异脚本补充2 套脚本一键检查表结构差异脚本优先迁移前自动对比 MySQL 源表 (catalog) 和 Doris 目标表提前找出缺失字段、字段类型不一致规避column xxx not foundCatalog 定时增量同步方案基于主键 ID / 更新时间戳增量拉取定时 crontab 执行前提沿用你现有环境Dorismysql_src catalogDWD 库table_list.txt 表清单1、表结构自动差异检查脚本check_table_diff.sh功能遍历table_list.txt对每一张表通过 catalog 读取源表mysql字段名、字段类型读取 Doris 目标表字段名、字段类型输出【Doris 缺失字段】【字段类型不一致】直接打印 SQLALTER TABLE ADD COLUMN ...一键复制执行放在迁移第一步执行提前发现问题不再导入中途报错中断#!/bin/bash # check_table_diff.sh 对比catalog源表与Doris目标表结构差异 # table_list.txt格式mysql_src.源库.源表,Doris库.目标表,主键,haspk/nopk DORIS_HOST127.0.0.1 DORIS_PORT9030 DORIS_USERroot DORIS_PASSroot20160118 TABLE_LISTtable_list.txt mysql_doris() { mysql -h${DORIS_HOST} -P${DORIS_PORT} -u${DORIS_USER} -p${DORIS_PASS} -N -s -e $1 } echo 开始检查表结构差异 while IFS read -r line;do [[ -z ${line} || ${line} ~ ^# ]] continue IFS, read -r src_full dst_full pk_col pk_type ${line} echo -e \n 对比表源${src_full} 目标${dst_full} # 获取源表字段字段名|类型 src_raw$(mysql_doris DESC ${src_full};) src_cols() declare -A src_dict while IFS$\t read -r cname ctype rest;do [[ -z $cname ]] continue src_cols(${cname}) src_dict[${cname}]${ctype} done ${src_raw} # 获取Doris目标表字段 dst_raw$(mysql_doris DESC ${dst_full};) dst_cols() declare -A dst_dict while IFS$\t read -r cname ctype rest;do [[ -z $cname ]] continue dst_cols(${cname}) dst_dict[${cname}]${ctype} done ${dst_raw} # 查找Doris缺失字段 missing() alter_sqlALTER TABLE ${dst_full} has_missing0 for c in ${src_cols[]};do if [[ ! -v dst_dict[$c] ]];then has_missing1 ctype${src_dict[$c]} missing(${c} : ${ctype}) alter_sql ADD COLUMN \${c}\ ${ctype}, fi done if [[ ${has_missing} -eq 1 ]];then echo 【Doris目标表缺失字段】 for m in ${missing[]};do echo ${m};done alter_sql${alter_sql%,}; echo 一键补字段SQL${alter_sql} else echo ✅ 字段数量匹配无缺失字段 fi # 检查类型不一致 echo -e \n【字段类型差异校验】 type_err0 for c in ${src_cols[]};do if [[ -v dst_dict[$c] ]];then s_t${src_dict[$c]} d_t${dst_dict[$c]} if [[ ${s_t} ! ${d_t} ]];then type_err1 echo ⚠️ 字段${c} 源类型:${s_t} | Doris类型:${d_t} fi fi done if [[ ${type_err} -eq 0 ]];then echo ✅ 字段类型全部一致 fi unset src_dict unset dst_dict done ${TABLE_LIST} echo -e \n 检查完成 使用方式# 赋权 chmod x check_table_diff.sh # 执行 ./check_table_diff.sh执行后直接输出缺失字段 完整 ALTER 语句复制到 Doris 客户端一次性执行。2、Catalog 定时增量同步方案两种模式二选一原理仍然复用 mysql_src catalog不改动源 MySQL通过增量条件拉取新增数据写入 Doris。 适用前提MySQL 源表必须存在增量标记字段二选一 方案 A推荐update_timedatetime 字段数据更新也会同步 方案 B自增主键 id仅新增数据更新旧数据不会同步⚠️重要增量模式只追加新数据如果源表有 UPDATEDoris 普通表不会覆盖旧数据需要支持更新Doris 目标表必须建 Unique Key 模型2.1 增量脚本gen_inc_sync.sh生成增量 SQL#!/bin/bash # gen_inc_sync.sh 基于时间戳/主键 生成增量同步SQL # table_list.txt 增加第5列增量标记字段如update_time 或者 id # 格式mysql_src.源库.源表,Doris库.目标表,主键,haspk,inc_col # 示例mysql_src.gz_wzy.t_reports_zxjb,DWD.wzy_t_reports_zxjb,id,haspk,update_time # mysql_src.gz_wzy.t_reports_zxjb,DWD.wzy_t_reports_zxjb,id,haspk,id DORIS_HOST127.0.0.1 DORIS_PORT9030 DORIS_USERroot DORIS_PASSroot20160118 TABLE_LISTtable_list_inc.txt OUTPUT_SQLmigrate_inc.sql LAST_RECORD_FILE./last_inc_point mysql_doris() { mysql -h${DORIS_HOST} -P${DORIS_PORT} -u${DORIS_USER} -p${DORIS_PASS} -N -s -e $1 } ${OUTPUT_SQL} mkdir -p ${LAST_RECORD_FILE} while IFS read -r line;do [[ -z ${line} || ${line} ~ ^# ]] continue IFS, read -r src_full dst_full pk_col pk_type inc_col ${line} echo 增量处理表: ${src_full} # 读取上一次同步位置 point_file${LAST_RECORD_FILE}/${dst_full//\//_} last_val if [[ -f ${point_file} ]];then last_val$(cat ${point_file}) fi # 获取源表全部字段 RAW_COL$(mysql_doris DESC ${src_full};) col_list while IFS$\t read -r cname ctype rest;do [[ -z ${cname} ]] continue col_list${col_list}\${cname}\, done ${RAW_COL} col_list${col_list%,} extra_insertdt,sync_time,data_source extra_selectCURDATE(),NOW(),mysql_gz_inc full_insert_cols${col_list},${extra_insert} full_select_cols${col_list},${extra_select} # 判断增量字段是时间还是id if [[ ${inc_col} update_time ]];then # 时间戳增量 if [[ -z ${last_val} ]];then last_valNOW() - INTERVAL 1 YEAR fi where_cond${inc_col} ${last_val} # 获取本次最新时间 new_max$(mysql_doris SELECT MAX(${inc_col}) FROM ${src_full} WHERE ${where_cond};) else # 主键id增量 if [[ -z ${last_val} ]];then last_val0 fi where_cond${inc_col} ${last_val} new_max$(mysql_doris SELECT MAX(${inc_col}) FROM ${src_full} WHERE ${where_cond};) fi if [[ ${new_max} NULL || -z ${new_max} ]];then echo -- ${src_full} 本次无新增数据 ${OUTPUT_SQL} continue fi echo -- ${src_full} 增量同步条件${where_cond} ${OUTPUT_SQL} echo INSERT INTO ${dst_full} (${full_insert_cols}) ${OUTPUT_SQL} echo SELECT ${full_select_cols} ${OUTPUT_SQL} echo FROM ${src_full} ${OUTPUT_SQL} echo WHERE ${where_cond}; ${OUTPUT_SQL} echo SELECT SLEEP(0.5); ${OUTPUT_SQL} # 保存本次最大标记点 echo ${new_max} ${point_file} done ${TABLE_LIST} echo 增量SQL生成完成${OUTPUT_SQL}赋权chmod x gen_inc_sync.sh2.2 增量执行脚本run_inc.sh#!/bin/bash ./gen_inc_sync.sh nohup /usr/local/mysql-8.0.26/bin/mysql --force -h127.0.0.1 -P9030 -uroot -proot20160118 migrate_inc.sql inc_run.log 21 echo 增量任务执行结束 $(date) inc_run.log赋权chmod x run_inc.sh2.3 配置定时任务 crontab# 编辑定时任务 crontab -e # 每10分钟执行一次增量 */10 * * * * /data/doris/run_inc.sh⚠️增量模式重点注意事项Doris 表模型选择只新增不更新旧数据使用 Duplicate Key需要同步 MySQL 的 UPDATE 修改Doris 目标表必须是Unique Key主键与 MySQL 主键保持一致断点续传脚本自动记录上一次同步的id/update_time下次从该位置继续不会重复拉历史数据源表如果删除数据catalog 方案无法感知删除只能新增 / 更新删除需要额外逻辑大表增量建议按 id 分片避免单次拉取数据量过大BE 内存溢出3、完整复用流程更新加入新增脚本全量迁移流程首次初始化./check_table_diff.sh自动检查表结构差异执行输出的 ALTER 补齐字段执行./gen_doris_sync.sh生成全量 SQL单条 SQL 预测试验证nohup 执行全量导入监控日志核对 count 行数完成初始化后续日常增量同步准备table_list_inc.txt填写增量字段配置 crontab 定时执行run_inc.sh定期查看inc_run.log日志监控报错周期性核对两边表 count校验数据一致性4、额外踩坑补充新增到方案文档增量脚本的记录文件last_inc_point不要手动删除删除会触发全量重新拉取时间增量模式MySQL 源表update_time必须更新数据时自动修改建表设置ON UPDATE CURRENT_TIMESTAMP否则更新的数据无法被捕获大表增量单次拉取数据量大建议在增量 SQL 增加LIMIT防止 Doris BE OOMcrontab 环境变量问题定时任务执行时 PATH 会变化脚本内尽量使用绝对路径可选增强增量同步失败告警钉钉 / 企业微信推送日志增量 SQL 增加 LIMIT 分页超大表防止一次性读取大量数据重复数据去重逻辑

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑