做MySQL迁移到达梦这件事,很容易被低估。表面看这就是"把表和数据倒过去",但真正动起手来你会发现,它更像一场方言翻译:同样的意思,MySQL说一套,达梦说另一套。表结构、数据类型、SQL写法、存储过程、应用驱动,每一层都藏着隐形的差异。这篇文章把我实际做过的一次完整MySQL到达梦数据迁移项目里最常踩的坑、最实用的流程、以及真正能提高成功率的方法一次性讲清楚。适合正在做国产数据库选型评估的DBA,也适合负责应用改造的Java开发,以及需要在达梦上维护数据的运维同学。
1. 迁移的真正难点:不是搬数据,是翻译"MySQL方言"
1.1 为什么很多人把数据迁移做成了"二次开发"
我见过不少项目,迁移方案一开始定的是"拿工具一键导过去",结果导入完成后应用全线报错。原因很简单:他们把迁移当成了搬运,而实际这是一次整体翻译。
举几个我最早没意识到、后来被现实教育的例子:
- MySQL建表语句里的
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4,达梦根本不认识。 - MySQL字段名可以用反引号包起来,达梦默认没有反引号这回事。
- MySQL的
int(11)里的11是显示宽度,达梦的INT就是INT,这个11没有任何存储意义。 - MySQL里表名用反引号包住的小写,到了达梦如果被工具转成大写且应用代码没注意,就会产生找不到表的报错。
这些差异不是靠数据搬移工具能解决的,因为它们发生在两个层面:数据层面解决的是"数据能不能放进去",方言层面解决的是"结构、SQL、代码能不能跑起来"。绝大多数迁移项目的痛苦,都来自后者。
我自己归纳了一个"迁移三层"概念,后面所有文章内容都围绕这三层展开:
- 结构层:建表语句、索引、约束、视图、存储过程、触发器。
- 数据层:实际的数据行、大字段、二进制内容、字符集转换。
- 执行层:应用里所有SQL语句、ORM框架、JDBC驱动、连接池配置。
这三层必须同时改造,缺一个都会导致上线失败。数据迁移只是其中一环,而且往往是最容易的一环。
1.2 达梦的兼容模式:开局先选对路
达梦数据库有一个 COMPATIBLE_MODE 参数,用来控制对外语法的兼容程度,常见取值包括兼容Oracle、兼容MySQL、兼容SQL Server等,具体以你当前版本的官方手册为准。
于是很多团队会问:既然要迁MySQL,那把达梦设成MySQL兼容模式不就行了?
我的建议是:不要把全部希望押在兼容模式上。开启MySQL兼容模式,确实可以让 LIMIT、反引号这类语法在某些场景下平滑通过,但数据库的整体语义、函数行为、系统表结构、SQL解析规则依然和MySQL有大量差异。依赖兼容模式去跑一个复杂业务系统,结果往往是这里兼容了、那里又炸了,排查起来比老老实实改造SQL更费劲。
我实际采用的做法是:以达梦官方推荐的标准写法为主,用兼容模式做辅助验证。也就是说,把MySQL的SQL改造成达梦能稳定执行的写法,而不是指望数据库自动"猜"出MySQL语法。这样迁移后的系统行为是可预期的,后续维护也省心。
还有一个和版本强相关的问题:达梦DM8对MySQL的兼容性明显好于DM7,所以如果是新项目,尽量选择DM8的较新版本。MySQL侧,5.7和8.0在字符集、JSON类型、窗口函数等特性上差异较大,迁移前要明确源端版本,避免在迁移过程中才发现某些特性在目标端支持度不足。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 迁移前必须摸清的底牌:版本、兼容模式与对象清单
2.1 版本和环境的坑:装对人可能就少了一半事
很多人迁移刚开始就卡在环境上。达梦数据库的安装方式主要有两种:Linux环境下的命令行安装,以及Docker方式。Docker方式对快速验证很友好,比如直接拉取官方镜像启动,映射好5236端口,很快就能得到一个可用的实例。Linux环境安装则在部署到正式服务器时更常见,比如在麒麟V10这类系统上安装,注意执行安装脚本时的用户权限和图形界面依赖问题。
连接工具同样容易踩坑。达梦官方自带的管理工具(Manager)是首选,但很多团队习惯了DBeaver或者Navicat这类工具。DBeaver连接达梦时,通常需要手动加载达梦JDBC驱动包(比如 DmJdbcDriver18.jar),驱动包路径配置错了会直接报 No suitable driver。Kettle这类ETL工具则要把驱动jar包放进程的lib目录下,并且重启工具才能识别。
我的建议是:初始化环境时,直接用官方工具把"到库"这条路先走通,再决定后续用什么工具做日常管理。目标库连接不上,后面所有迁移步骤都无从谈起。
2.2 对象清单盘点:迁移范围不是一张表
数据迁移的边界,绝不只是表和索引。我每次都会带着团队先做一次对象盘点,把迁移范围明确到清单级别:
- 业务表、分区表、临时表
- 视图、物化视图(如果有)
- 索引、主键、唯一约束、外键
- 存储过程、函数、触发器
- 事件调度器/定时作业
- 序列、自增属性
- 数据库账号权限
MySQL里的 EVENT(事件调度器)在达梦中没有完全等价物,通常要重建为达梦的作业系统。序列在MySQL里用得不多,但达梦对序列的支持非常接近Oracle风格,如果业务依赖自增ID,迁移时可以用达梦的 IDENTITY 或序列来实现。
除了对象清单,数据量摸底也很重要。我一般会先跑一遍统计脚本,把每个表的行数、估算大小、是否有大字段(TEXT/BLOB)、是否有特殊字符(比如emoji)列出来:
sql复制SELECT
table_name,
table_rows,
data_length + index_length AS total_size,
table_collation
FROM information_schema.tables
WHERE table_schema = 'your_db'
ORDER BY total_size DESC;
这个摸底动作帮我筛出后续必须分批处理的大表,也避免了迁移时才发现某些表数据量远超预期。
3. 表结构与约束迁移:类型映射表就是藏宝图
3.1 常用数据类型映射清单
表结构迁移的第一件事,是把MySQL的数据类型翻译成达梦类型。我整理了一张常用映射表,是我在项目里逐条验证过的:
| MySQL类型 | 达梦类型 | 注意事项 |
|---|---|---|
| TINYINT | SMALLINT | MySQL的TINYINT(1)常用于布尔,建议在业务层统一处理 |
| INT/INTEGER | INT | 忽略显示宽度,如int(11)直接映射为INT |
| BIGINT | BIGINT | 注意值域,达梦BIGINT是64位,足够覆盖 |
| VARCHAR(n) | VARCHAR(n) | 最关键:MySQL按字符计数,达梦受LENGTH_IN_CHAR参数影响,可能按字节计数 |
| TEXT | TEXT/CLOB | 建议使用CLOB,兼容性和大字段长度都有保障 |
| DATETIME | TIMESTAMP | 达梦TIMESTAMP精度更细,一般业务直接使用 |
| TIMESTAMP | TIMESTAMP | 注意默认值CURRENT_TIMESTAMP在达梦中的写法 |
| DECIMAL(p,s) | DECIMAL(p,s) | MySQL支持到65位,达梦一般上限38位,超长需业务调整 |
| BIT/BOOLEAN | SMALLINT | 达梦有BIT类型,但为了ORM通用性我常用SMALLINT替代 |
| JSON | TEXT/CLOB | 达梦较新版本支持JSON类型,不确定时先用CLOB过渡 |
| ENUM/SET | VARCHAR + CHECK约束 | MySQL特有,建议转换为VARCHAR并在应用层校验 |
| BLOB | BLOB | 达梦BLOB支持二进制大对象 |
这张表最值得划重点的是 VARCHAR(n) 这一行。MySQL里的 VARCHAR(255) 意思是能存255个字符,而达梦在 LENGTH_IN_CHAR=0(默认按字节)的情况下,UTF-8编码里一个汉字占3字节,意味着同样的 VARCHAR(255) 存汉子只能存85个,数据导入时就会报长度超限。
解决方式有两个层面:一是安装达梦时把 LENGTH_IN_CHAR 设为1,让 VARCHAR(n) 按字符长度解释;二是迁移前评估每个字段的最大字符数,把目标字段长度按3倍余量扩大。我通常两个都做,双保险。
3.2 自增列、默认值、注释和关键字冲突
MySQL里最常见的自增写法是:
sql复制CREATE TABLE t_user (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(50),
PRIMARY KEY (id)
) ENGINE=InnoDB;
达梦的标准写法是用 IDENTITY:
sql复制CREATE TABLE t_user (
id INT IDENTITY(1,1) NOT NULL,
name VARCHAR(50),
PRIMARY KEY (id)
);
注意一张达梦表只能有一个IDENTITY列。如果MySQL表里同时存在自增列和联合主键,迁移时逻辑要梳理清楚,别把自增列之外的约束弄丢。
默认值方面,MySQL的 DEFAULT CURRENT_TIMESTAMP 在达梦中要写成 DEFAULT CURRENT_TIMESTAMP 或者 DEFAULT SYSTIMESTAMP,取决于版本。注释在MySQL是直接写在字段定义里,达梦则要用 COMMENT ON COLUMN 语句单独追加,DTS工具通常能帮你转换,但手工建表时很容易漏。
关键字冲突是我反复踩的一个坑。MySQL里一些很普通的列名,比如 rank、comment、condition、level,在达梦里可能是保留字。如果原表恰好用了这些名字,迁移到达梦后要么加双引号包起来,要么直接改字段名。我的做法是启动一个"冲突扫描":把源库所有表字段名和达梦保留字表比对一遍,提前找出需要处理的字段。
还有一个更隐蔽的坑:索引重名。MySQL中不同表可以有同名索引,而达梦的索引在同一模式下不允许重名。迁移时如果原库有多个表都叫 idx_status 之类,达梦会直接报对象已存在。这一条在DTS自动迁移时偶尔会被忽略,最后应用上线某个查询走了全表扫描才发现。
4. 数据搬移实操:DTS、批量导入与数据校验
4.1 达梦数据迁移工具DTS的常规流程
对象盘点完成、表结构在目标端建好之后,就可以进入实际的数据搬移环节。达梦官方提供了一个图形化迁移工具DTS,我建议把它作为中小型项目的第一选择。
DTS的基本流程是:
- 新建迁移工程,选择源MySQL连接。
- 配置目标达梦连接。
- 选择要迁移的对象。
- 在迁移前做类型映射调整。
- 执行迁移,观察日志和错误记录。
实际使用中有几个优化点:
- 先导结构、后导数据、再导约束和索引:如果一次性把外键、索引全部带过去,导入大表时约束检查会拖慢速度。我习惯先只建表,导入全部数据后再补主键、唯一约束、外键和索引。
- 遇到异常不要直接重跑全部:DTS支持分批和断点续跑,批量导入过程中个别表报错很正常,把报错表单独拎出来修复,比全量重跑效率高得多。
- 大字段单独验证:如果表里有BLOB或CLOB,导入后一定要抽查数据是否完整,尤其是包含emoji、特殊编码的中文内容。
4.2 大表分批与工具链选择
DTS虽然方便,但数据量超过一定规模后,单靠图形化工具导大表,时间和稳定性都是问题。我给一个经验分界:
- 百万行以下:DTS或任意工具都够用。
- 百万到千万行:建议按主键或时间做分批导出导入,每批几万到几十万行。
- 千万行以上:建议用DataX、Kettle这类ETL工具,按自定义分片并发抽取。
DataX的典型思路是:MySQL reader + 达梦writer。注意官方DataX默认没有达梦writer,需要引入社区适配版本,或者参照现有writer源码自己改一个。配置里可以控制 channel 并发数和单次 batchSize,我一般从4并发、每批1000条起步,边跑边看目标端磁盘IO,再逐步调高。
Kettle连接达梦的坑主要在驱动加载:把 DmJdbcDriver18.jar 放入lib目录后,连接配置里驱动类填 dm.jdbc.driver.DmDriver,URL填 jdbc:dm://IP:5236,不要漏掉端口。Kettle在跑大表的时候内存控制要注意,避免同时打开太多转换步骤导致OOM。
如果后续还有持续同步需求,可以关注SeaTunnel对达梦CDC的支持。它适合做变更数据捕获,能在初始迁移后把增量变更准实时地同步到目标库,对割接窗口短的项目很有价值。
4.3 数据校验:count、抽样与明细对账
数据导入完成不算完,校验没过等于白导。我每次都要做三层校验:
第一层:行数校验。 对每个表分别执行:
sql复制SELECT COUNT(*) FROM source_schema.t_xxx;
SELECT COUNT(*) FROM target_schema.t_xxx;
这个操作看起来简单,却是最有效的兜底。如果两张表行数不一致,再查差异数据。
第二层:抽样一致性。 取每个表的主键最大值、最小值,再随机抽几百行重点字段做对比。这部分可以用自动化脚本,也可以借助Kettle的比对步骤。抽样要覆盖到边界数据:比如空字符串与NULL、最大日期、最大数值、特殊字符。
第三层:约束校验。 检查目标端是否有重复数据、唯一键冲突、外键失效。MySQL和达梦对NULL、空串的处理不完全一致,批量导入后很容易出现唯一索引上同时存在NULL的情况,这在某些表设计里是允许的,但在另一些表里会直接破坏业务逻辑。
校验工具上,除了手写SQL,也可以用DBeaver的对比能力或者专门的比对工具。对于大表,count太慢时可以先比对 MAX(id) 和 MIN(id),再配合抽样,能省下不少时间。
5. 存储过程和应用层改造:最容易翻车的地方
5.1 存储过程、触发器与函数:方言改造现场
数据库迁移做到这一步,基本已经解决了"表能不能建、数据能不能进"的问题,接下来真正考验人的是业务逻辑层。如果是纯MySQL的存储过程直接丢到达梦执行,基本都会报语法错误。
举个例子,一个最简单的MySQL存储过程:
sql复制DELIMITER //
CREATE PROCEDURE sp_get_user(IN v_id INT)
BEGIN
DECLARE v_name VARCHAR(50);
SELECT name INTO v_name FROM t_user WHERE id = v_id;
IF v_name IS NULL THEN
SET v_name = 'unknown';
END IF;
SELECT v_name;
END //
DELIMITER ;
同样的逻辑,达梦里更贴近Oracle风格:
sql复制CREATE OR REPLACE PROCEDURE sp_get_user(v_id IN INT)
AS
v_name VARCHAR(50);
BEGIN
SELECT name INTO v_name FROM t_user WHERE id = v_id;
IF v_name IS NULL THEN
v_name := 'unknown';
END IF;
PRINT v_name;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
差异点非常多:MySQL用 DELIMITER 分隔,达梦不需要;MySQL的变量赋值用 SET,达梦用 :=;MySQL游标可以直接 DECLARE cur CURSOR FOR SELECT ...,达梦的游标声明更接近Oracle;异常处理上MySQL用 DECLARE EXIT HANDLER,达梦用 EXCEPTION WHEN ... THEN。
我处理存储过程的思路是:先列清单,再按复杂度排序,最后逐个手改。达梦提供了部分迁移辅助能力,但经过自动化转换的代码我仍然会做人工评审。存储过程这种地方出错,不会像数据导入那样立刻报错,而是等到某个业务场景触发才暴露,排障成本很高。
触发器也是重灾区。MySQL的 FOR EACH ROW 达梦同样支持,NEW 和 OLD 也能用,但触发器的创建语句里如果有 DEFINER、TINYINT 这类MySQL特性,就必须处理。
还有视图。MySQL视图里常见的 ALGORITHM=MERGE、SQL SECURITY DEFINER 这些修饰符在达梦里得去掉;视图中的反引号、IFNULL、DATE_FORMAT 也需要逐一替换。视图迁移后我习惯立刻执行一次 SELECT COUNT(*) 或 DESC,确认视图能正常解析。
如果源库用了事件调度器,达梦里要转成作业系统。这一步容易漏,因为事件调度器往往扮演着定时跑批、清理日志的角色,漏掉之后可能几周后才有人发现业务数据一直没有被归档。
5.2 JDBC驱动、连接串与ORM框架适配
数据和服务端对象迁移完成后,应用层的改造直接决定系统能否跑起来。
第一步是换JDBC驱动。MySQL用的驱动类是 com.mysql.cj.jdbc.Driver,达梦对应的驱动类是 dm.jdbc.driver.DmDriver,URL格式从 jdbc:mysql://IP:3306/dbname 变成 jdbc:dm://IP:5236?schema=SCHEMA_NAME。这里的 schema 指定连接默认模式,很关键,否则应用可能把对象建到了错误的用户名模式下。
连接池配置也要同步改。Druid连接池配置里,driverClassName 和 dbType 都需要更新,Druid的较新版本支持 dbType=dm,能正确识别达梦的SQL方言。HikariCP则需要修改数据源配置。如果配置没改成,启动时最容易报 Cannot load driver class。
第二步是处理ORM框架的分页和方言。很多Java项目用MyBatis-Plus或者PageHelper做分页,MyBatis-Plus需要把数据库类型设为 DM,PageHelper的分页方言也要配置为 dm,否则分页SQL可能会用上MySQL的 LIMIT,在达梦里直接语法报错。
第三步是清理SQL方言残留。我把常见的函数差异整理成一张速查表:
| MySQL写法 | 达梦推荐写法 | 说明 |
|---|---|---|
| LIMIT offset, size | 达梦兼容模式或使用ROWNUM/ROW_NUMBER() | 最影响分页功能 |
| IFNULL(a, b) | NVL(a, b) 或 COALESCE(a, b) | 函数名不同 |
| NOW() | SYSDATE 或 CURRENT_TIMESTAMP | 取当前时间 |
| DATE_FORMAT(d, '%Y-%m-%d') | TO_CHAR(d, 'YYYY-MM-DD') | 格式表达式不同 |
| GROUP_CONCAT(x) | LISTAGG(x, ',') | 聚合拼接 |
| UUID() | SYS_GUID() | 生成唯一标识 |
| CONCAT(a, b, c) | CONCAT(a, b, c) | 达梦也支持,但参数边界有差异 |
建议把这些差异做成一个正则扫描清单,在代码仓库里全局搜索替换。这步听起来琐碎,但漏掉任何一个 IFNULL 在运行时都会变成SQLSyntaxErrorException。
顺便提一句,现在很多开源框架本身已经做了达梦适配,比如常见的后台管理框架RuoYi、芋道这类项目。你在改造自己业务前,可以先去看看这些框架的达梦适配分支或文档,参考它们的方言配置和建表脚本,能少走很多弯路。
6. 上线前的验证清单与典型坑位
6.1 功能回归、性能对比与回滚设计
迁移后上线不是"导完数据就能用",我坚持要做一轮完整的功能回归。回归重点覆盖三块:列表分页、报表统计、所有包含存储过程和函数的业务动作。
分页验证时,点开首页、末页、随机中间页,对比排序结果;报表统计重点验证聚合函数和日期格式化的结果;存储过程验证要注意输入边界值,比如空参数、超大参数、带特殊字符的参数。
性能对比同样不能省。达梦的优化器行为和MySQL不一样,原来在MySQL上走索引的SQL在达梦上可能因为统计信息缺失而走了全表扫描。迁移后要执行一次统计信息更新:
sql复制SP_TAB_STAT_INIT('SCHEMA_NAME', 'T_USER');
然后再跑一遍核心SQL,对比执行计划。如果发现某些SQL性能差,通常优先检查目标端索引是否建全、统计信息是否过期、以及SQL中的隐式类型转换。
回滚设计一定要提前做。我每次迁移都会明确回滚条件:如果验证阶段出现数据不一致、核心功能不可用,就立即停止割接,把应用指向回MySQL。为此源端MySQL必须保留完整备份,目标端达梦也要做一次备份快照。备份方式上,达梦可以用命令行工具 dexp/dimp 导出导入,单实例恢复到DSC这类场景要额外确认归档日志的连续性。
6.2 高频坑位速查表
踩过不少坑之后,我把出现频率最高的问题整理成了一张速查表,给团队其他人排查时用:
| 现象 | 根因 | 解决思路 |
|---|---|---|
| 导入达梦时字段长度超限 | VARCHAR按字节计数,中文3字节占位 | 设置LENGTH_IN_CHAR,或扩展字段长度 |
应用报 -6602 这类错误码 |
多为数据长度、类型转换或对象状态问题 | 先查目标端执行日志,定位具体SQL,再对比字段定义 |
| GROUP BY查询报错 | MySQL允许的宽松分组在达梦更严格 | 把SELECT中非聚合字段全部加入GROUP BY |
| 分页返回结果异常 | LIMIT语法没有正确转换 | 调整ORM方言配置,或改写为ROWNUM |
| 找不到表或字段 | 大小写语义不一致,达梦默认转大写 | 去掉代码中引号,或统一使用大写标识符 |
| 索引冲突 | MySQL允许不同表同名索引,达梦不允许 | 迁移前做索引重名扫描并改名 |
| 反引号报错 | MySQL专用语法 | 全局替换反引号为普通标识符 |
| 自增ID跳跃 | 达梦IDENTITY分配有缓存机制 | 应用不要依赖严格连续ID |
| 大表导入速度极慢 | 外键和索引未延迟创建 | 先导数据再建索引和约束 |
这张表不是替代排查,而是给大家一个快速着手的方向。真正遇到问题的时候,最重要的还是先定位到具体SQL和目标端日志,再结合达梦的错误代码手册去判断。
迁移这种事情,做一次和做两次的体会完全不同。我第一次做类似的迁移项目时,想的是"赶紧导完赶紧上线",结果上线之后连续一周都在处理SQL兼容问题。第二次开始,我把大部分时间花在了对象盘点、SQL扫描和方言改造上,真正导数据的操作反而只用了很少的时间。如果你也在准备做一次MySQL到达梦的迁移,我的建议是:不要追求一次搬完,先搭一个最小业务闭环跑通全链路,再逐步扩大迁移范围。数据搬不过去可以硬搬,业务逻辑翻译不透彻才是真正卡住上线的地方。
