186-6908-0178
当前位置:首页 >程序开发
从 MySQL 到达梦:一次信创隔离环境里的数据库迁移踩坑实录
2026-09-16 59次浏览

从 MySQL 到达梦:一次信创隔离环境里的数据库迁移踩坑实录

数据库迁移这件事,听起来通常很简单。

源库导出,目标库导入。

如果两边数据库不同,再加一层结构转换。

理论上,大概就是:

lua代码解读复制代码MySQL   ↓ dump   ↓ 转换   ↓ DM8

真正做起来,我才发现,到了信创环境,这四步几乎每一步都可能出问题。

这次的环境是比较典型的信创项目:

代码解读复制代码源数据库:MySQL 5.7 目标数据库:达梦 DM8 目标操作系统:银河麒麟 网络环境:隔离网络 
生产环境:已经部署在信创区 数据流向:只能进入,不能导出

最开始我以为,最大的麻烦会是 MySQL 和达梦之间的 SQL 方言兼容性。后来才发现,SQL 方言只是问题的一部分。

真正麻烦的是,数据库兼容性、迁移工具、网络隔离、Schema 机制、生产约束,全部叠加到了一起。

最后这件事变成了一次很典型的“看起来简单,实际处处有坑”的异构数据库迁移。

一、第一条弯路:直接把 MySQL dump 灌进达梦

最直觉的办法当然是:

代码解读复制代码mysqldump database > database.sql

然后:

ini代码解读复制代码START database.sql;

如果只是 MySQL 到 MySQL,这没什么问题。

但 MySQL dump 本质上不是一种“通用 SQL 格式”,而是大量混合了 MySQL 方言和 mysqldump 控制语句的文件。

例如:

ini代码解读复制代码/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
 /*!40014 SET FOREIGN_KEY_CHECKS=0 */;

还有:

ini代码解读复制代码ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT UNSIGNED ON UPDATE CURRENT_
TIMESTAMP

以及 MySQL 常见的:

arduino代码解读复制代码COMMENT '字段说明'

这些东西到了 DM8 里,并不能指望全部直接执行。

所以第一条经验很简单:

异构数据库迁移时,不要把 mysqldump 理解成通用数据库备份格式。

它更准确的定义是:

MySQL 可以重新执行的一套 SQL 和控制语句。

而不是:

其他数据库能够直接消费的数据交换格式。

二、DTS 能解决问题,但并不是“一键迁移”

后来开始尝试达梦自带的 DTS 数据迁移工具。DTS 的确很有用,它至少可以完成:

sql代码解读复制代码MySQL SQL    ↓ 语法分析    ↓ 类型映射    ↓ DM SQL

而且它还有数据库对象评估功能。 这次一个库评估出来的结果甚至是:

sql代码解读复制代码339 张表 兼容率 100% 不兼容对象 0 错误 SQL 0

看起来已经非常理想。但这里很容易产生一个误解:

“评估兼容率 100%”不等于“生成的 SQL 可以 100% 直接执行”。

事实上后面依然遇到了很多问题。 例如 DTS 转换结果里会出现:

ini代码解读复制代码/****DMDTS CONVERT*** ENGINE=InnoDB*/

以及:

ini代码解读复制代码/****DMDTS CONVERT*** DEFAULT CHARSET=utf8mb4*/

还有一些 MySQL 原始语句没有被彻底清理:

ini代码解读复制代码/*!40101 SET character_set_client = @saved_cs_client */;

甚至某些字段会被错误转换。

例如原 MySQL:

arduino代码解读复制代码discount_rate decimal(10,2) unsigned NOT NULL

转换后居然变成:

arduino代码解读复制代码"discount_rate" NOT NULL

字段类型直接丢了。所以后来我逐渐形成一个判断:

DTS 应该被视为“辅助转换器”,而不是最终裁判。

它能大幅减少人工转换工作量,但生成的 SQL 仍然需要:

代码解读复制代码转换 → 清洗 → 执行 → 校验 → 修补

而不是:

代码解读复制代码转换 → 上生产

三、真正有效的办法:先迁结构,再迁数据

这次迁移过程中,最重要的一次思路调整,就是不再试图把:DDL + 数据 一次性全部解决。

而是明确拆成两条链路。

结构:

css代码解读复制代码MySQL schema      ↓ mysqldump --no-data      ↓ DTS 转换    
  ↓ 清洗 DM DDL      ↓ DM 建表

数据:

kotlin代码解读复制代码MySQL data      ↓ data-only dump / CSV      ↓ 转换或批量装载   
   ↓ DM

这样最大的好处,是问题被拆小了。以前一个巨大 SQL 文件失败,只知道“导入失败”。 拆开以后可以明确知道:

代码解读复制代码是结构的问题?还是数据的问题? 是第 17 张表?还是第 17 张表的第 300 行? 是字段类型?
还是数据内容?

对异构数据库迁移来说,可定位性非常重要。

四、第一次结构迁移:339 张表,最后成功 324 张

第一个数据库原本有:339 张表

DTS 转换后的 SQL 一开始执行时,一张都没有建出来。 检查后发现转换文件里依然混杂了大量 MySQL 内容:

arduino代码解读复制代码/*!40101 ... */

以及 DTS 的说明性注释:

arduino代码解读复制代码/****DMDTS CONVERT*** ... */

还有 MySQL 风格列注释:

sql代码解读复制代码"id" bigint NOT NULL COMMENT '主键'

于是对整个 SQL 做了一次批量清洗:

sql代码解读复制代码删除 /*!xxxxx ... */ 删除 DTS CONVERT 注释块 删除 CREATE SCHEMA 删除 SET SCHEMA 删除
 ENGINE / CHARSET 等残留 转换 COMMENT 转换 b'1' / b'0'

其中字段注释从:

sql代码解读复制代码"id" bigint COMMENT '主键'

改为 DM 风格:

sql代码解读复制代码"id" bigint

再单独:

vbnet代码解读复制代码COMMENT ON COLUMN "table"."id" IS '主键';

处理以后重新执行。结果: 339 张 成功 324 张 失败 15 张

这时候就没有必要再反复执行整份 SQL 了。

直接对比:

sql代码解读复制代码SELECT TABLE_NAME FROM USER_TABLES;

和原始的 339 张表名单,就能精确得到缺失的 15 张。

五、15 张失败表告诉了我一件事:问题往往是成批出现的

这 15 张失败表并不是 15 个不同的问题。

实际上只集中在几个模式。

1. AUTO_INCREMENT

部分表包含:

代码解读复制代码AUTO_INCREMENT

尤其还有一些比较特殊的字段:

scss代码解读复制代码DECIMAL AUTO_INCREMENT

或者并非主键的自增列。这种情况下,与其强行保证第一次建表就完全还原 MySQL 语义,不如先以“能正确导入历史数据”为目标。

因此第一次建表时先去掉:

代码解读复制代码AUTO_INCREMENT

历史数据本身会显式写入 ID,并不会受到影响。 自增语义可以等结构和数据迁完以后,再根据业务实际情况恢复。 这也体现出数据库迁移里一个很重要的原则:

迁移过程中,不一定要一步恢复所有运行时语义。可以先保证数据正确,再恢复约束和自动化行为。

2. ON UPDATE CURRENT_TIMESTAMP

另外几张表共同包含:

sql代码解读复制代码ON UPDATE CURRENT_TIMESTAMP

DTS 转换以后变成了:

sql代码解读复制代码ON UPDATE LOCALTIMESTAMP

但这个转换结果并不能直接作为 DM 的列定义执行。去掉以后,表立即能够正常创建。 如果业务确实依赖自动更新时间,后续可以再通过:应用逻辑、触发器、其他 DM 机制 恢复。

3. 字段类型转换丢失

前面提到的:

scss代码解读复制代码decimal(10,2) unsigned

转换后变成:

arduino代码解读复制代码NOT NULL

这种问题最危险。因为它不是“语法错误”,而是转换过程里丢失了字段语义。

因此迁移后一定要抽查:

ini代码解读复制代码DESC table_name;

至少要覆盖:

sql代码解读复制代码BIGINT DECIMAL VARCHAR DATETIME TEXT BLOB BIT TINYINT

不能只看:

代码解读复制代码339/339

表数量相等只是第一层验证。

4. 外键创建顺序

Quartz 的几张表:

代码解读复制代码qrtz_blob_triggers qrtz_cron_triggers qrtz_simple_triggers qrtz_simprop_triggers

都依赖:

代码解读复制代码qrtz_triggers

如果子表先创建:

scss代码解读复制代码FOREIGN KEY (...) REFERENCES qrtz_triggers(...)

而父表还不存在,就会失败。 最后采用的办法非常直接:

代码解读复制代码先去掉外键 → 全部建表 → 后续再补外键

实际上我后来觉得,这可能本来就是异构迁移时更合理的办法。因为外键并不是“数据存在”的前提。反而迁移大量数据时,暂时没有外键通常更方便。

六、第二个库又踩了一遍,但坑不完全一样

第二个数据库有:

代码解读复制代码291 张表

清洗后第一次成功:

代码解读复制代码283 张

少了 8 张。

这一次主要遇到了两个新问题。

一个是 DTS 生成了明显错误的:

scss代码解读复制代码DEFAULT CURRENT_TIMESTAMP(3) (3)

这种语句当然无法执行。

另一个是 MySQL 里存在:

scss代码解读复制代码DECIMAL(65,0) DECIMAL(65,2)

而 DM 对 DECIMAL 精度的支持范围不同。

最后统一改成:

scss代码解读复制代码DECIMAL(38,0) DECIMAL(38,2)

8 张表补完后,整个结构才最终完整。

这也进一步证明:

同样是 MySQL → DM,不同业务库出现的问题不一定相同。

工具可以减少工作量,但不能替代迁移验证。

七、一个非常隐蔽的坑:Schema 和用户

这次还有一个让我印象特别深的坑。

DM 的用户和 Schema 关系,与 MySQL 的“数据库”概念并不完全一样。因为前面为了清理 DTS SQL,我删除了:

sql代码解读复制代码CREATE SCHEMA SET SCHEMA

这意味着脚本会直接在:

当前登录用户

下面建表。结果有一次我用:SYSDBA

执行了业务库结构 SQL。后来一查:

sql代码解读复制代码SELECT OWNER, COUNT(*) FROM DBA_TABLES GROUP BY OWNER;

出现:

代码解读复制代码SYSDBA         285 YYJX_YGJ_PROD 339

我才意识到,第二套业务表被建到了:SYSDBA 下面。

这件事本身很好修。但需要记住:

执行结构脚本之前,不要只确认数据库地址和端口,一定确认当前用户。

在执行之前固定先看:

sql代码解读复制代码SELECT USER;

再执行:

sql代码解读复制代码SELECT COUNT(*) FROM USER_TABLES;

确认 Schema 环境无误以后再:

ini代码解读复制代码START schema.sql;

这一条看似简单,但生产迁移里真的非常重要。

八、为什么 SQL 里一个   都能让导入停下来

迁移到数据阶段以后,又遇到一个非常奇怪的问题。DIsql 不断提示:

代码解读复制代码输入 nbsp 的值: 输入 nbsp 的值: 输入 nbsp 的值:

第一反应会怀疑 SQL 文件编码坏了。最后才发现,业务数据里本身包含大量 HTML:

ini代码解读复制代码  " < >

DIsql 默认支持变量替换,而:& 恰好是变量前缀。 所以它看到: 会理解为:变量名 nbsp。于是开始要求人工输入变量。

解决办法只有一句:

sql代码解读复制代码SET DEFINE OFF;

这件事让我重新认识了“数据迁移”。 以前往往只考虑:

代码解读复制代码字段类型 字符集 主键 索引

但真实业务数据里还有:

css代码解读复制代码HTML JSON XML 特殊字符 转义字符 长文本

任何一个都可能碰到数据库客户端自己的语义。

九、信创隔离环境最大的难点,其实不是数据库

做到后面我越来越觉得,这次迁移最大的困难甚至不是 MySQL 和 DM 的兼容性。

而是:

生产环境已经进入信创隔离区,而且数据只能进,不能出。

这意味着很多我们平时习惯的操作都不存在了。

例如:生产导一份数据出来分析:不行。

失败以后把 SQL 和日志拿出来处理:可能也不行。

外部工具直连生产数据库:更不行。

于是整个迁移流程必须重新设计。

传统环境可以:

代码解读复制代码迁移 → 发现问题 → 导出来 → 修改 → 再迁

单向隔离环境里更合理的是:

代码解读复制代码外部准备 → 一次性摆渡完整迁移包 → 内部执行 → 内部验证

即:

进入隔离环境之前,就应该尽可能把后续可能需要的工具、SQL、校验逻辑一起准备进去。

十、后来我开始把迁移看成“发布”,而不是“拷数据”

如果再让我做一次类似项目,我不会再准备一个:

代码解读复制代码database.sql

然后拿进去碰运气。而是准备一个完整的迁移包:

erlang代码解读复制代码migration_package/ ├── 01_schema/ │   ├── schema.sql │   
└── patch.sql │ ├── 02_data/ │   ├── table_a.sql │   ├── table_b.sql │ 
  └── ... │ ├── 03_verify/ │   ├── source_count.txt │   ├── verify_count.sql │ 
    └── verify_business.sql │ ├── 04_rollback/ │   └── rollback.sql │ └── README.txt

执行顺序固定:

代码解读复制代码Precheck → DDL → 数据 → 校验 → 提交

这其实已经不像传统意义上的“数据库迁移”。更像一次数据库发布。而且生产已经在隔离区以后,以后每一次补数都应该有:

代码解读复制代码输入 影响范围 批次边界 校验方法 回滚方式

而不是临时手工执行 SQL。

十一、数据校验不要只做 COUNT(*)

表数量一致

ini代码解读复制代码339 = 339

只能证明“表基本建齐”。数据迁移也是一样。

比如:

sql代码解读复制代码SELECT COUNT(*) FROM member_info;

两边都是:

代码解读复制代码100000

并不能证明数据完全正确。更稳妥的校验至少可以做:

scss代码解读复制代码SELECT     COUNT(*) AS CNT,     MIN(id) AS MIN_ID,     MAX(id) AS MAX_ID,  
   SUM(id) AS SUM_ID FROM member_info;

如果业务允许,还可以做一些状态分布:

sql代码解读复制代码SELECT status, COUNT(*) FROM member_info GROUP BY status;

或者:

scss代码解读复制代码SELECT DATE(create_time), COUNT(*) FROM member_info GROUP BY DATE(create_time);

这些统计结果比单纯行数更容易发现漏数、重复导入等问题。

而且特别适合:

数据不能导出,但可以在隔离区内部执行校验 SQL

的环境。

十二、最终总结出来的几个原则

这一轮折腾以后,我现在对 MySQL → DM 的理解大概变成了下面这些。

1. 不要追求“一把梭”

异构数据库迁移最好拆成:

代码解读复制代码结构 数据 约束 运行时语义

四个阶段。

2. 转换工具只是助手

无论评估结果多漂亮,都不应该认为:自动转换结果 = 最终结果

3. 先让表存在,再恢复高级语义

例如:

sql代码解读复制代码AUTO_INCREMENT ON UPDATE FOREIGN KEY TRIGGER

都可以在数据迁完以后恢复。它们不应该阻塞基础迁移。

4. 错误要批量归类

15 张表失败,不意味着有 15 个问题。

通常是:

代码解读复制代码4 种语法 导致 15 张表失败

找到共同模式以后,批量修复远比一张张改有效。

5. 每一步都要可验证

结构:源 = 目标 对象:INVALID = 0 数据:COUNT 、MIN、MAX、SUM、业务分布

否则“执行成功”并不意味着“迁移成功”。

6. 单向隔离环境里,迁移包比迁移工具更重要

当生产数据只能进不能出以后,真正可靠的不是某个 GUI 工具。

而是一套:可离线、可重复、可校验、可回滚、可审计 的迁移流程。

结语

最开始我只是想把一个 MySQL dump 导进达梦。后来一路碰到了:

sql代码解读复制代码JDBC DTS SQL 方言 Schema AUTO_INCREMENT ON UPDATE DECIMAL 精度 外键顺序 HTML 实体
 DIsql 变量替换 网络隔离 生产数据单向流

最后才意识到,这件事真正考验的并不是会不会写几条 SQL。而是能不能在一个约束很多、信息并不完整、工具也不完全可靠的环境里,把一个大问题拆成若干可验证的小问题。

从这个角度看,所谓“信创数据库迁移”,真正需要迁移的不只是数据。还有原来那些默认成立的假设。

比如:

  • 网络应该是通的。

  • SQL 应该是兼容的。

  • 工具转换出来的东西应该能执行。

  • 出了问题可以把数据拿出来分析。 当这些假设一个个失效以后,迁移方案才真正开始成形。而这大概也是这次折腾里,最值得记录下来的东西。


作者:Sailor_Infra
链接:https://juejin.cn/post/7685644759736844323
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


回到顶部