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

从 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 应该是兼容的。
  • 工具转换出来的东西应该能执行。
  • 出了问题可以把数据拿出来分析。 当这些假设一个个失效以后,迁移方案才真正开始成形。而这大概也是这次折腾里,最值得记录下来的东西。
相关推荐
数据工匠老o1 小时前
sysbench/TPC-C/自定义脚本:数据库压测工具对比与实战流程
数据库·测试
程序员天天困1 小时前
Kafka 接入 AI 的三条路线:MCP 提案、会话记忆与实时上下文
大数据·后端·kafka
明月_清风1 小时前
用自然语言控制 Blender:开源项目 Blender MCP 深度介绍
人工智能·后端
数安3000天2 小时前
数据分类分级产品有哪四类技术路线?
大数据·数据库
泡茶喝茶写代码2 小时前
量化数据开发实战系列(第 25 篇):基金基础接口实战:基金列表、ETF-LOF 分类、基金概况、净值数据
java·数据库·人工智能·python·mysql
张小姐的猫2 小时前
【AI大模型接入SDK】 —— 数据管理 & 与Session模块进行联动
数据结构·数据库·c++·人工智能·python·chatgpt
IvorySQL2 小时前
PostgreSQL 日报|大模型破解在线校验和(9 月 15 日)
数据库·人工智能·postgresql
JaguarJack2 小时前
Whim 是什么?一门把 PHP 被否决的设想真跑起来的实验语言
后端·php·服务端
明月_清风3 小时前
MHS:AI Agent 开始连接物理世界
人工智能·后端·网络协议