做数据库迁移这件事,我一开始是有点轻视的。项目要把一套跑了好几年的 MySQL 5.7 业务库迁到达梦 DM8,规模不大,也就几十个库、几百张表,但我接手之后才发现,“MySQL迁移到达梦”这个任务真正的难度根本不在导出导入,而在你对两边数据库差异的认知深度。文章里我不会去复述官方文档,只讲我在实际项目里摸索出来的完整迁移路径、踩过的坑,以及最后沉淀下来可以直接参考的检查项。
1. 为什么MySQL迁移到达梦不是“换个数据库”这么简单
1.1 达梦不是MySQL的克隆版,语法兼容只是“看起来像”
很多第一次做迁移的人,最容易犯的错就是把达梦当成一个“MySQL兼容数据库”,以为把表结构搬过去、把数据导进去、改个连接串就完事了。实际接触 DM8 之后你会发现,它确实在尽力兼容 MySQL 的常用语法,比如支持limit、支持auto_increment、支持部分on duplicate key update语义,但这种兼容注定是“尽力”,不是“全等”。我见过最典型的例子:同一个 SQL 在 MySQL 里跑得好好的,到达梦里因为函数名不同、参数行为不同,直接报错。比如date_format()在达梦里不认,要换成to_char();group_concat()不认,要换成listagg();ifnull()两边行为也有差异。
更要命的是,达梦默认的大小写敏感规则、标识符处理方式、以及表名是否加双引号,都会直接影响 SQL 在应用侧的表现。如果应用代码里大量使用反引号包裹表名和字段名,迁移到 DM8 之后每一个反引号都要处理,这就不是“迁移”,已经是“改写”了。
1.2 迁移工作必须拆成三个层面来推进
我在实际项目里把整个迁移工作拆成了三层:结构层、数据层、应用层。结构层负责把表、索引、视图、序列、存储过程这些对象从 MySQL 的定义转换成达梦能识别的定义;数据层负责把记录搬过去并且保证不丢、不错、不乱;应用层负责让业务系统跑在达梦之上,SQL、驱动、连接池、事务行为全部要重新验证。这三层里最容易出问题的是最后一层,因为结构层和数据层都是“一次性”工作,而应用层是“长期”适配,需要不停回归测试。
如果你的项目只是迁移一批历史数据过去做离线分析,那简单很多,数据导过去校验完就结束。但如果是要替代生产库、让业务系统长期运行,应用层的适配工作量会占到整个项目的六成以上。别贪快,把三层拆开分配人力,每一层都设一个明确的验收标准,比闷头干活要高效得多。
2. 动手之前,先做一次彻底“体检”再做迁移方案
2.1 版本、字符集、大小写敏感,这三个问题能劝退一半人
做迁移方案之前,必须先搞清楚源头数据库的版本和配置。MySQL 5.6、5.7、8.0 在数据类型、默认字符集、SQL 行为上差异不小,达梦的迁移工具对不同版本 MySQL 的支持程度也不一样。我建议先把select version();和show variables like '%character_set%';的结果存下来,作为迁移评估的输入。别小看字符集,常见的情况是:源库是utf8mb4,目标库建库时用了默认字符集,数据导完之后中文全部变成乱码或者问号。达梦的字符集在建库时就已经固定,后面很难改,这个坑一旦踩进去,只能重建实例再来一次。
大小写敏感问题更容易被忽视。MySQL 在 Linux 上默认表名大小写敏感,在 Windows 上默认不敏感,达梦也有自己的大小写处理规则。如果源库的表名有的大写、有的小写,迁移到 DM8 之后很可能出现“表或视图不存在”这种一看就让人头大的报错。我的建议是:迁移前统一表名、字段名为小写,应用侧 SQL 也统一小写且不加引号,这样两边兼容性最好。
2.2 迁移工具选型:图形化工具和命令行脚本要配合用
达梦官方提供的 DTS 数据迁移工具是图形化界面,用起来很直观,适合做结构迁移和数据迁移。它的操作逻辑是:新建工程、选择迁移任务、指定源库类型为 MySQL、指定目标库类型为 DM8、选择要迁移的对象、设置类型映射关系、执行迁移。一次能迁移多个库,也能保留索引、约束、注释,整体体验比我预想的好。
不过 DTS 不是万能的。对象数量特别多的时候,图形化界面一个个点很累,而且工具默认的映射规则不一定符合你的需求。我习惯先用 DTS 跑一遍结构迁移,把表结构、索引、约束这些基础对象搬过去,然后用自己写的脚本做数据迁移的补充和校验。复杂对象比如存储过程、触发器、定时任务,DTS 能做基础的语法转换,但转换完不等于能跑,必须人工审查改写的正确性。
如果你的环境不允许图形化操作,比如目标库在隔离内网,也可以通过达梦提供的一些命令行工具处理数据导入。但 DTS 无论如何都值得在迁移测试阶段先跑一遍,因为它的日志和错误提示能帮你快速暴露两边不兼容的具体问题。
2.3 迁移前的检查清单和评估表格
我把项目里用的检查清单整理成了一张表,每次迁移前都会逐项确认:
| 检查项 | 确认内容 | 备注 |
|---|---|---|
| MySQL版本 | 5.7/8.0,是否使用特殊分支 | 版本影响语法转换 |
| 字符集 | 源库utf8mb4还是utf8 | 目标库建库时保持同字符集 |
| 排序规则 | 是否区分大小写 | 影响表名/字段名匹配 |
| 存储引擎 | 是否存在 MyISAM 表 | 统一转成 DM 的默认存储 |
| 对象清单 | 表、视图、存储过程、触发器的数量 | 评估工作量 |
| 数据量 | 每张大表的行数和预估容量 | 决定迁移批次和时长 |
| 自增列 | 是否有auto_increment | 转换成IDENTITY或序列 |
| 特殊类型 | JSON、ENUM、SET 使用情况 | 需要改写 |
| SQL特征 | 反引号、group_concat等关键字 | 应用侧改写工作量 |
| 外键/触发器 | 是否有循环依赖 | 迁移顺序要先拆依赖 |
| 定时任务 | MySQL event 是否存在 | 达梦对应任务机制 |
这张表不复杂,但在项目启动前过一遍,能让你的方案从“感觉可以”变成“有依据地可以”。我见过有人跳过这些直接开干,结果迁移到一半发现源库里有几十个event,达梦根本不认,临时加需求,工期直接崩掉。
3. 开始迁移:从建库到数据校验的完整实操
3.1 建库与模式准备:表空间、页大小和用户权限
达梦的库、模式和表之间的关系和 MySQL 不太一样,第一天最好就把目标库的逻辑结构规划清楚。我的做法是:创建一个业务用户,用户默认模式就是业务模式,所有表都建在这个模式下,应用连接的账号就是这个用户。这样做的好处是权限好控制,后续备份恢复也方便。
建库时最重要的事是确认页大小。DM8 的页大小在建库时指定,可选的是 4K、8K、16K、32K,它直接影响行长度上限和 CLOB 字段的支持。如果需要存大的文本内容,页大小太小的实例表可能都建不出来,甚至报错。我当时测试环境用了默认参数,后面迁移一张含有大字段的表直接失败,才知道要重建实例换大页。所以如果源库存在 TEXT、MEDIUMTEXT、LONGTEXT 类型的字段,建库页大小直接选 16K 或者 32K,不要犹豫。
建完库之后,把源库需要迁移的索引、外键约束先别急着建。我的习惯是先只迁移表结构和数据,再把索引和外键补上,这样导入阶段少了很多约束检查的开销,大批量插入时速度明显更快。触发器也建议先禁用,数据导完校验一致后再启用并做完整性测试。
3.2 结构迁移:类型映射与特殊对象处理
DTS 在做结构迁移时已经内置了一张 MySQL 到 DM8 的类型映射表,但默认映射不总是最优的。我给一张常用的映射关系表,做结构迁移前对照一下:
| MySQL类型 | DM8映射 | 说明 |
|---|---|---|
| TINYINT | SMALLINT | 按长度和取值范围对应 |
| INT/INTEGER | INT | 直接映射 |
| BIGINT | BIGINT | 直接映射 |
| VARCHAR(n) | VARCHAR(n) | 注意DM8按字符计算长度 |
| TEXT | CLOB | 大对象类型 |
| LONGTEXT | CLOB | 大对象类型 |
| DATETIME | DATETIME | 注意默认值处理 |
| TIMESTAMP | TIMESTAMP | 注意时区差异 |
| DECIMAL(p,s) | DECIMAL(p,s) | 精度尽量保持一致 |
| BLOB | BLOB | 二进制对象 |
| JSON | CLOB或VARCHAR | 应用侧序列化处理 |
| ENUM | VARCHAR + CHECK约束 | 转换成约束,更稳妥 |
| SET | VARCHAR | 用逗号分隔存储 |
这里特别想说的是VARCHAR的长度单位。MySQL 里VARCHAR(255)表示 255 个字符,达梦里同样写VARCHAR(255)也是字符,但如果应用依赖字节长度,可能在存储超长数据时被截断。所以结构迁移完成后,要抽查几张表看看字段长度是否按预期保留。
对于ENUM和SET这种 MySQL 特色类型,我的建议是不要试图找一一对应的类型,直接转成VARCHAR并补上CHECK约束,能保证数据合法性,成本也低。JSON类型则看达梦版本支不支持,如果支持可以用,如果不支持,就转成CLOB并让应用负责序列化和反序列化,注意应用侧不能直接用 MySQL 的JSON_EXTRACT()这类函数。
3.3 数据迁移:全量同步与分批调优
结构迁移完成后,数据迁移反而相对机械,但也要讲究方法。我先用 DTS 做全量数据迁移,源库选择 MySQL,目标库选择 DM8,迁移前设置好批量提交的行数。批量值太小,网络往返频繁,速度起不来;批量值太大,内存占用高,长事务还可能拖慢数据库。项目里我常用的是每批 1000 到 5000 行,具体要看字段宽度和网络延迟。
DTS 迁移大表时,界面上能看到每个表的导入进度。如果某张表报错,日志里会写明出错行和原因,常见的是数据类型超范围、字符串超长、时间格式无法识别。遇到这种问题,不要着急在源库改数据,先看日志定位,再去源库确认这条数据的真实情况,必要时调整映射规则重新迁移这张表。
数据迁移还有一个容易被忽略的点:外键依赖顺序。如果表之间有外键关系,迁移数据时必须先导父表再导子表,否则子表的数据插不进去。DTS 默认会按依赖顺序执行,但如果你自己写脚本导数据,就要在生成 INSERT 语句时做好排序。
迁移过程中我会留一份完整的数据量统计,比如每张表的行数、聚合字段的SUM、主键最大值等。这份统计既用于迁移后的校验,也能在迁移中途发现问题时快速定位是哪一批数据出了问题。
3.4 数据校验:不只对比行数,还要看这几点
很多人在数据迁移完只跑一个select count(*)对比行数,行数一致就觉得成功了。这远远不够。行数一致只能说明记录条数没少,但不能保证字段值没变、顺序没乱、类型没被隐式转换。我吃过这个亏:有一次有一张表行数完全一致,但某个字段从DECIMAL(10,2)被映射成了DECIMAL(8,2),导致超过范围的数据被四舍五入,账目对不上,差点酿成大问题。
我的校验做法分四步:
- 对比每张表的行数;
- 对比主键的最大值和最小值,确认没有丢边界数据;
- 随机抽取若干张表,对某几个关键字段做
SUM对比,数值型字段用SUM,字符串字段用COUNT(DISTINCT)辅助判断; - 针对大字段表,抽查几条记录的字段长度是否一致。
这套校验流程看起来笨,但能覆盖绝大多数迁移产生的数据异常。如果项目还有专门的测试团队,建议留出时间让他们跑一遍核心业务流程,因为程序读数据的方式和人工校验不一样,只有真实业务才能暴露最深层的兼容问题。
4. 应用侧适配:真正拉开工作量的大头
4.1 驱动与连接串改造
数据库迁移完,应用侧第一件要改的是 JDBC 驱动和连接串。MySQL 的驱动是com.mysql.cj.jdbc.Driver,达梦对应的是dm.jdbc.driver.DmDriver。连接串由jdbc:mysql://ip:3306/库名改成jdbc:dm://ip:5236/用户名?schema=模式名,端口默认是 5236。
这里有几个容易踩的坑:达梦的 JDBC URL 末尾可能需要带schema参数来指定默认模式,否则应用 execute 时可能找不到表;还有连接池配置里initialSize、maxActive这些参数名和 MySQL 版本也不一致。所以我每次改造连接串后都会先写一个极简的 JDBC 测试程序跑通查询,再交给应用团队去改连接池,避免两边的沟通成本都浪费在“连不上”这类最基础的问题上。
4.2 常用SQL语法差异速查与改写示例
应用侧的 SQL 改写是整个迁移项目里最琐碎也最考验耐心的部分。把我在项目里遇到的高频差异整理成了一张速查表:
| MySQL写法 | 达梦DM8改写 | 说明 |
|---|---|---|
LIMIT n | LIMIT n或TOP n | 部分版本和场景用ROWNUM |
IFNULL(a, b) | NVL(a, b) | 两者语义相近 |
NOW() | SYSDATE | 达梦支持但写法不同 |
DATE_FORMAT(d, fmt) | TO_CHAR(d, fmt) | 格式占位符有差异 |
GROUP_CONCAT(col) | LISTAGG(col, ',') | 注意LISTAGG语法 |
ON DUPLICATE KEY UPDATE | MERGE INTO | 手动逻辑改写 |
REPLACE INTO | MERGE INTO | 手动逻辑改写 |
反引号\col`` | 不使用反引号 | 大小写敏感场景要小心 |
举个例子,MySQL 里很常见的select * from t where create_time = date_format(now(), '%Y-%m-%d'),到达梦里就要改成select * from t where create_time = to_char(sysdate, 'yyyy-mm-dd')。看着只差一个函数名,但格式串不同,应用逻辑就可能出问题。
再比如group_concat在 MySQL 里可以把分组的多行字段拼成一个字符串,很多报表系统都依赖它。达梦里对应功能是listagg,但listagg对超长字符串的默认处理方式和 MySQL 也有差异,改写时最好限定长度或者明确指定分隔符。这类差异一定不能让开发人员靠记忆去改,最好把常用差异整理成速查文档,内置到代码检查规则里,提交代码时自动扫一遍。
4.3 存储过程、触发器与函数迁移要点
如果业务系统里有大量存储过程和触发器,迁移工作量会明显增加。DTS 能把 MySQL 的存储过程转换成达梦的存储过程,但转换完成并不代表能直接跑,语法细节必须人工审。
我遇到的一个典型案例是:存储过程里有一个循环,MySQL 里直接写while,达梦也支持while,但循环里的变量赋值、游标声明方式有差异,转换后第一遍编译就报错。另一个常见问题是存储过程中使用动态 SQL,达梦的EXECUTE IMMEDIATE和 MySQL 的PREPARE语法不同,需要逐条改写。
函数迁移也有自己的坑。比如 MySQL 的字符串拼接函数CONCAT(a, b, c),如果某个参数为NULL,在 MySQL 里结果是NULL,但达梦的一些版本对NULL拼接的处理不一样,应用如果依赖这一点,逻辑就要调整。我的建议是给存储过程和函数单独建一个迁移批次,迁移完每个对象都要做一次编译验证和行为测试,不要等到应用联调时才暴露。
5. 常见问题与避坑实录:这些问题能省你一周时间
5.1 自增列与序列的兼容性处理
MySQL 的自增列靠auto_increment实现,达梦里对应的是自增列属性IDENTITY。如果表结构迁移后自增属性丢了,后续插入就要自己维护一个序列,代价不小。我在迁移前会专门检查一遍所有含自增列的表,确认 DTS 完成后每张表的IDENTITY还在。
自增列还有一个隐藏问题:迁移数据时如果显式插入主键值,达梦的IDENTITY当前值不会自动更新。比如源表主键最大是 100,迁移后你插入了 ID=100 的历史数据,但自增列的下一个值还是从 1 开始,业务新增记录时就会撞主键。解决办法是迁移完手动修改自增列的当前值,可以用 SQL 把序列或者自增列调整到合适的位置。每次迁移完都要记得处理,这个坑我在测试环境踩了两次才长记性。
5.2 大字段与超长字符串的截断问题
项目里有一张表记录用户反馈内容,字段类型是LONGTEXT,真实数据里最长有上万字符。DTS 迁移时默认映射成 CLOB,但目标库页大小不够,导入时报错。后面重建了页大小为 32K 的实例,这个问题才解决。
除了页大小,还要注意字段校验规则的差异。MySQL 的VARCHAR(255)在非严格模式下,超长字符串会被自动截断并告警,而达梦对超长字符串的报错逻辑可能不同,要么直接报错,要么静默截断。应用如果写入超长数据,表现会完全不同。迁移后要专门用一批超长字符串做测试,确认达梦的行为是否符合业务预期。
5.3 时间类型与默认值陷阱
MySQL 的DATETIME默认值可以写成CURRENT_TIMESTAMP,达梦也支持,但精确到秒还是毫秒、是否带时区,两边处理有细节差异。如果应用写数据不主动传时间,完全依赖数据库默认值,迁移后可能出现时间精度变化,或者时区偏移,进而影响业务判断。这个问题的排查很隐蔽,因为表面上数据都正常,但报表统计的时间范围总有边界差。
我的建议是:迁移前把源库中所有带默认值的时间字段列出来,逐个确认达梦的默认值语义。如果两个库时间精度不一致,宁可应用侧显式传时间,也别依赖数据库默认。
5.4 并发插入性能落差与批量提交调优
迁移完成后应用联调阶段,测试团队反馈批量导入接口变慢了。排查发现,相同的数据量,MySQL 下并发插入几百条非常快,达梦下同样逻辑却出现明显延迟。这里面原因比较复杂,可能是达梦对事务提交、索引维护的机制和 MySQL 不同,也可能是应用连接池的配置没有跟上。
调整手段主要有两个方向:一是加大批量提交的批次,比如从单条提交改成每 500 条提交一个事务;二是检查索引数量,导入阶段不必要的索引可以先去掉。还有一个小技巧是调整达梦相关的缓存参数,但这需要 DBA 评估,不要自己在生产环境乱调。性能问题一定要在联调阶段集中压测,别等到上线后再发现。
5.5 迁移过程里最容易被忽略的两件小事
最后分享两个不复杂但容易忽略的点。一是定时任务,MySQL 里用event管理定时任务,达梦有自己的作业系统,对象迁移不会自动把event带过去。如果没有梳理源库的定时任务清单,上线后会发现报表没人定时刷新、清理任务没跑。二是日志表和历史数据,这类表通常增长很快,冷热数据差异大,迁移时可以考虑按时间分区处理,避免一张超级大表拖累整体迁移效率。
迁移这活,真正做完之后回头看,技术上并没有哪个点是“不可能会”的,难的是每个点都可能出问题,而问题出现的时间往往在你想不到的地方。我个人实际操作中的体会是:把检查清单做在前面,把校验动作做得比想象中更细,应用侧适配不要压缩时间,这个项目就成功了一大半。希望这些经验能让你在接手 MySQL 迁移到达梦的任务时,少走一些我走过的弯路。