去年帮客户做了一套业务系统从 MySQL 往达梦数据库(DM8)迁移的活儿,整个过程中踩的坑、改的脚本、总结的方案,比预想中要多得多。MySQL 和达梦虽然都是关系型数据库,SQL 大体上长得像,但真到了逐条语句、逐个函数、逐个数据类型的层面,差异非常细碎。这篇文章就把我在实际迁移中遇到的 SQL 语法问题、处理方式以及整体迁移方案完整梳理出来,给后续要做类似迁移的朋友一个参考。
1. 迁移前必须想清楚的几件事
1.1 明确迁移目标:是"兼容"还是"重写"
拿到达梦数据库这件事之后,第一步不是打开工具开始导数据,而是先要搞清楚迁移的最终形态。市面上很多项目所谓的"从 MySQL 迁移到达梦",其实分两种情况:
第一种是尽可能兼容。达梦 DM8 本身有 MySQL 兼容模式,可以把它当作一个"方言比较接近 MySQL"的数据库来用。这种情况下,SQL 改写的工作量小很多,很多 MySQL 的写法可以直接跑,前提是达梦的兼容参数配置得当。
第二种是彻底原生改造。完全按照达梦的习惯来重写 SQL,包括分页、自增列、函数写法、日期处理等。这种方案最稳,但工作量翻倍。
实际项目中,我通常建议走"兼容优先、原生兜底"的路线:先把达梦的兼容参数打开,让大部分 SQL 能跑通,再针对少数报错的语句精修。如果一开始就想着全部重写,项目周期和风险往往不可控。
1.2 摸清家底:先做一次全面的 SQL 资产盘点
迁移之前,务必要做一次彻底的 SQL 扫描。很多人以为把表结构和数据搞过去就完事了,真正的难点全部在应用代码里的 SQL。这步做不好,后面上线就是灾难。
具体盘点内容至少包括三块:
- 表结构清单:涉及哪些表、每张表的主键类型、自增字段用什么方式实现、有没有 enum/set/json 这类 MySQL 特有类型、字段默认值是否依赖 MySQL 函数(比如 DEFAULT CURRENT_TIMESTAMP)。
- SQL 语句清单:把应用代码里的 SQL 全部捞出来,重点检查分页写法(LIMIT)、函数调用(DATE_FORMAT、IF、IFNULL 等)、锁语法(SELECT ... FOR UPDATE)、批量插入(INSERT ... ON DUPLICATE KEY UPDATE)。
- 存储过程与触发器:MySQL 的存储过程语法和达梦差异很大,如果业务里有大量存储过程,这块要单独安排改造时间。我见过一个项目 200 多个存储过程,光是改写就花了两周。
1.3 工具链准备:达梦自带的几板斧
达梦数据库安装完成之后,自带几个关键工具,迁移前要先把它们用熟:
- DM 管理工具:图形化界面,类似 MySQL Workbench,主要用于表结构查看、SQL 调试、数据浏览。
- DTS 数据迁移工具:这一条最关键。达梦自带的 DTS 支持从 MySQL 直接迁数据,可以连表结构和数据一起搬,是迁移工作的主力工具。
- disql 命令行工具:类似 mysql 命令行,适合跑批量脚本、做语法验证。
工具准备好之后,我习惯先把目标库的初始化参数配一遍,特别是影响 SQL 兼容性的那几个:COMPATIBLE_MODE设置为 MySQL 模式,字符集建议用 UTF-8,大小写敏感的配置要想清楚。这些参数牵一发动全身,一旦数据迁移进去再改,代价非常大。
2. SQL 语法差异:最容易踩爆的雷区
2.1 数据类型差异:一张表看懂"长得像但不通用"
数据类型是迁移时第一个照妖镜。MySQL 里很多常用类型,到了达梦并不是直接对应过去,写错的结果要么是建表失败,要么是数据精度丢失。
| MySQL 类型 | 达梦 DM8 对应类型 | 迁移说明 |
|---|---|---|
| TINYINT | SMALLINT | MySQL 的 TINYINT(1) 常用来存布尔值,达梦里对应 SMALLINT |
| INT/INTEGER | INT | 可以直接用 |
| BIGINT | BIGINT | 可以直接用,但注意达梦 BIGINT 的显示宽度语法不适用 |
| VARCHAR(n) | VARCHAR(n) | 注意字符语义:达梦 VARCHAR 长度默认按字节算,UTF-8 下中文要占 3 字节,上线前必须验证 |
| TEXT | TEXT/CLOB | 大字段建议用 CLOB,兼容性更稳 |
| DATETIME | DATETIME | 可以直接用,但默认值写法不同(见下文) |
| TIMESTAMP | TIMESTAMP | 达梦的 TIMESTAMP 精度更高,默认值处理不一样 |
| DECIMAL(m,d) | DECIMAL(m,n) | 语法相同,但注意精度上限 |
| ENUM | 不支持 | 迁移时改成 VARCHAR + CHECK 约束,或者让应用层约束 |
| JSON | 达梦支持 JSON 类型(DM8 有) | 如果达梦版本较老,建议改成 CLOB 存文本,应用层自己解析 |
重点说一下最坑的两个:
第一个是VARCHAR 的长度语义。MySQL 里VARCHAR(255),意思是 255 个字符。达梦默认情况下VARCHAR(255)是 255 个字节。如果你的表里存中文,255 字节只能放 85 个汉字。迁移后短数据都会报"字符串长度超出限制",当时产品被这个问题折腾得够呛。解决方式有两个:一是建表时手动把长度乘以 3(比如 MySQL 的 VARCHAR(255) 在达梦写成 VARCHAR(765)),二是在建库时把LENGTH_IN_CHAR参数设为 1,让 VARCHAR 长度按字符计算。这个参数必须在建库时确定,后期没法动态改,迁移前一定要想清楚。
第二个是ENUM 类型。MySQL 的 ENUM 在达梦里没有对应物,DTS 工具迁移的时候会有三种表现:直接转成 VARCHAR、报错、或者转成不太合适的类型。我建议提前把 ENUM 字段在源库侧改成 VARCHAR,一次性搞定,别依赖工具的自动转换。CHECK 约束可以用达梦的CONSTRAINT ... CHECK (col IN (...))替代,效果基本等价。
2.2 自增列:AUTO_INCREMENT 迁移到 IDENTITY
MySQL 的自增列写法是id INT AUTO_INCREMENT PRIMARY KEY,达梦里对应的是id INT IDENTITY(1,1) PRIMARY KEY。
DTS 迁移建表语句的时候,工具通常能识别 AUTO_INCREMENT 并转换成达梦的 IDENTITY,但有两个细节要注意:
一是显式插入自增值。MySQL 允许往自增列显式插入值(只要不撞主键),达梦默认不允许。很多老系统迁移的时候,业务代码里会有类似INSERT INTO t(id, name) VALUES (100, 'x')的语句,达梦下会直接报错。需要修改应用代码去掉 id 字段,或者对表执行SET IDENTITY_INSERT 表名 ON(达梦语法和 SQL Server 类似)。注意这个开关在达梦里是有会话级别的限制的,不能全局开。
二是自增列的缓存机制。MySQL 的自增列重启后会重置缓存,达梦也有类似行为,但两边在并发插入时的表现略微不同。迁移后建议做一次并发写入压测,确认自增值没有冲突、没有跳号异常。还有一个隐藏点:达梦的 IDENTITY 列在建表以后不能轻易修改,有些业务后期要改自增起始值,达梦这边调整要重建表,代价比 MySQL 大,这一点要提前跟业务方说清楚。
2.3 反引号问题:MySQL 的习惯在达梦里是语法错误
MySQL 里大家写表名、列名都喜欢用反引号包裹,比如SELECT `name` FROM `user`。达梦的默认方言是不认识反引号的,直接报语法错误。
这个消息对从 MySQL 迁移过来的人来说几乎是必踩的坑。处理方式:
- 如果是少量 SQL,直接改掉反引号,达梦默认可以用双引号包裹标识符。
- 如果 SQL 特别多,可以改达梦的兼容参数(
COMPATIBLE_MODE=4即 MySQL 兼容模式),部分情况能支持反引号。
这里要提醒一句:达梦的双引号有特殊意义。达梦默认的标识符是大写且不敏感的,一旦你用双引号包了一个小写表名,比如"user",达梦会认为这是一个区分大小写的对象名。之后所有针对这个表的引用都必须带着双引号且大小写一致,否则就会报"无效的表名"。迁移时如果遇到"明明表在那里,SQL 却查不到"的情况,十有八九是双引号的大小写敏感问题。我的建议是:迁移后的表名、列名统一用大写,应用 SQL 里也别带引号,省掉一堆麻烦。
2.4 函数差异:换了一张脸的同一个人
函数层面是迁移中最琐碎、最费时间的部分。下面列几个我实际碰到的高频差异:
IFNULL 和 NVL。MySQL 的IFNULL(a, b)在达梦里面最接近的是NVL(a, b),达梦也有IFNULL函数,但部分版本的实现和 MySQL 不完全一样,稳妥起见直接用NVL。这个差异看起来小,但应用代码里用的频率极高,全局替换即可。
IF 函数。MySQL 的IF(expr, a, b)在达梦里没有完全对应的单函数,需要用CASE WHEN expr THEN a ELSE b END替代。如果业务代码里有大量 IF 嵌套,改写起来比较费眼神,最好写个正则先粗筛一遍,再人工核对。
DATE_FORMAT 和 DATE_TO_CHAR。MySQL 里格式化日期的DATE_FORMAT(now(), '%Y-%m-%d'),达梦对应的是TO_CHAR(SYSDATE, 'YYYY-MM-DD')。注意格式串的占位符不一样:MySQL 用%Y、%m、%d,达梦用YYYY、MM、DD。这个也是最容易漏的错误,改了函数名没改格式串,跑完出来一堆乱码日期。
NOW() 和 SYSDATE。MySQL 里NOW()、CURRENT_TIMESTAMP都很常用,达梦里最稳妥的写法是SYSDATE。CURRENT_TIMESTAMP达梦也认,但在默认值场景下可能有限制,建议统一替换。
CONCAT 拼接。MySQL 里CONCAT(a, b)函数在达梦里依然存在,可以直接用。但要注意,MySQL 里字符串拼接也支持||操作符,达梦同样支持。实际中遇到的问题是两个字段拼接时有一个为 NULL,MySQL 的 CONCAT 会返回 NULL,达梦也有些微妙差异,最好在测试环境逐个验证。
GROUP_CONCAT。MySQL 的GROUP_CONCAT在达梦里对应的是LISTAGG或者WM_CONCAT。两个函数的排序和去重语法不完全一致,改写时建议把内部的ORDER BY拿出来单独处理。
STR_TO_DATE。MySQL 的STR_TO_DATE(str, format)对应达梦的TO_DATE(str, format)。同样注意格式串差异。
2.5 分页查询:LIMIT 不等于删掉重写
MySQL 的分页写法是:
SELECT * FROM t ORDER BY id LIMIT 10, 20;意思是跳过 10 条取 20 条。达梦的分页写法有好几种,最推荐的是用LIMIT的变体或者ROWNUM。
达梦 DM8 在兼容模式下也支持 LIMIT 语法,但细节有差异。稳妥的写法是:
SELECT * FROM t ORDER BY id LIMIT 10 OFFSET 20;或者干脆用传统的 ROWNUM 方式,比如:
SELECT * FROM ( SELECT t.*, ROWNUM AS rn FROM ( SELECT * FROM t ORDER BY id ) t WHERE ROWNUM <= 30 ) WHERE rn > 10;这里强烈建议:如果项目里分页 SQL 很多,统一封装一套分页模板,不要每个开发各写各的。迁移的时候把模板改掉一次,比改几十个分页语句靠谱得多。另外注意 LIMIT 后面跟变量的时候,达梦的写法可能要求先声明变量,不能直接在 SQL 里LIMIT ? OFFSET ?,这会影响 MyBatis 这类 ORM 框架的分页插件,迁移后一定要回归测试分页功能。
2.6 批量插入与冲突处理:ON DUPLICATE KEY UPDATE 的替代方案
MySQL 的INSERT ... ON DUPLICATE KEY UPDATE ...在达梦里默认是不支持的,这是我迁移时被应用开发问得最多的一句。
达梦的替代方案有两个:
MERGE INTO语句,详见下一节。- 达梦从某个版本开始也支持
INSERT ... ON DUPLICATE KEY UPDATE,但需要确认版本,而且行为可能和 MySQL 有细微差别(比如对自增列的影响)。
实际项目里,我更建议用MERGE来改写批量更新插入逻辑。写起来虽然比 MySQL 啰嗦一些,但行为可控,且达梦对 MERGE 的支持非常好,性能也有保障。
2.7 空字符串与 NULL 的相爱相杀
MySQL 里''和NULL是两种不同的值,但在很多场景下 MySQL 的排序和比较会对 NULL 有一些特殊处理。达梦对 NULL 的处理整体更接近 Oracle:NULL 参与比较运算的结果是未知,COUNT(字段)不计 NULL,ORDER BY默认 NULL 值排在最后(MySQL 则相反)。
迁移后最容易出问题的场景:
WHERE 字段 <> 'x'在 MySQL 里会排除 NULL 值,达梦也一样,但如果字段本身有 NULL,结果行为可能让业务意外。ORDER BY 字段 ASC时,NULL 值的排序位置两边不一致。- 唯一索引对 NULL 的处理:MySQL 唯一索引允许多个 NULL,达梦默认也是允许多个 NULL,这个两边倒是比较一致,但建议实测确认版本行为。
我的建议是:迁移后先跑一遍数据对比脚本,重点关注 NULL 值占比较高的字段,把这些字段的排序、比较逻辑全部回归一遍。
3. 迁移方案的完整落地步骤
3.1 方案选型:DTS 迁移还是导出导入
达梦提供的从 MySQL 迁移的路径主要有三种:
- 达梦 DTS 工具:图形化操作,可以连源 MySQL 库直接抽数据到达梦,支持表结构、数据、部分约束的迁移。适合中小规模项目,是我用得最多的方案。
- MySQL 导出 SQL + 手工调整后导入:先用 mysqldump 导出,手动改掉语法不兼容的部分,再在达梦里执行。这种方式看起来原始,但对复杂表结构的掌控力最强,适合表少但每个表都很复杂的场景。
- 达梦的迁移服务或第三方 ETL 工具:适合大规模、跨网络、需要增量同步的场景。如果数据量大到在线迁移时间窗口不够,就得考虑数据同步工具做增量。
我个人的经验是:优先选 DTS 做结构和全量数据迁移,SQL 脚本的方式做辅助和补充。DTS 能省掉大量手工建表的工作,但 DTS 自动生成的建表语句不一定完全符合预期,一定要对迁移结果做一次全面核对。
3.2 实操:使用 DTS 完成全量迁移
DTS 迁移的流程大致如下:
- 打开达梦 DTS 工具,新建迁移任务。
- 数据源选择 MySQL,填好 IP、端口、用户名、库名。注意 MySQL 连接驱动要提前装好,达梦 DTS 一般自带 MySQL 驱动,但版本如果和你的 MySQL 不匹配,连接阶段就会报错。
- 目标端选达梦数据库,指定目标模式和表空间。
- 对象选择时,默认会勾选表、视图、存储过程等。我建议第一次只勾选表和主键、索引,存储过程和视图先跳过,避免迁移过程报错中断。第一批先把表和纯数据迁完,再单独处理视图和存储过程。
- 执行迁移后,查看迁移报告。DTS 会记录每张表的迁移结果、行数、失败原因。这一步千万别跳过,要逐条看失败记录。
- 迁移完成后在达梦管理工具里抽查:对比表数量、行数、重点字段值。最好写几个校验 SQL,比如每张表做
SELECT COUNT(*)对比。
有一个细节:DTS 在迁移表结构时,默认可能会把AUTO_INCREMENT转成达梦的IDENTITY,但也会出现建表成功却不带自增属性的情况。迁移完以后一定要检查每张表的自增主键是否真的自增。
3.3 手工改写让你崩溃的存储过程
存储过程和触发器是迁移工作中最消耗精力的部分。
MySQL 的存储过程语法和达梦有几个关键差异:
- 定界符:MySQL 习惯用
DELIMITER $$来包裹存储过程,达梦不支持这个语法。 - 变量声明:MySQL 是
DECLARE var INT DEFAULT 0;,达梦需要DECLARE var INT := 0;(或者用DEFAULT有的版本支持,但:=更稳妥)。 - 游标循环:MySQL 的
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;这类写法,达梦语法完全不一样,需要改成达梦的游标循环方式。 - 异常处理:MySQL 的
SIGNAL SQLSTATE和达梦的RAISE完全不同。
正因为差异密集,我建议给存储过程制定一个"逐条翻译"的标准流程:先分析原存储过程的逻辑结构,画出流程图(注意并不是让你在文档里画图,而是在脑子里理清逻辑),然后按达梦语法重写。确认逻辑等价之后再进测试环境跑,不要贪快一次性批量转。
3.4 视图迁移:别被表面成功骗了
视图的迁移有个陷阱:DTS 能把视图创建语句搬过去,但达梦不保证视图内引用的函数和语法都能执行。很多时候视图建成功了,一查询就报错。
所以我迁移视图后有一个固定动作:把每个视图都 SELECT 一遍,验证是否能查出数据。视图嵌套多的情况下,建议从最底层的视图开始逐层验证,哪一层报错改哪一层。
3.5 应用层适配:连接驱动与配置修改
数据库侧的迁移完成只是半程,应用连不上、连上了跑不对照样白搭。
- JDBC 驱动:MySQL 的
com.mysql.cj.jdbc.Driver要换成达梦的dm.jdbc.driver.DmDriver,URL 格式从jdbc:mysql://ip:3306/db换成jdbc:dm://ip:5236/db。注意达梦默认端口是 5236,不是 3306。 - 连接池参数:MySQL 连接池的
autoReconnect、useSSL等参数达梦不认识,多余的参数要去掉。字符集参数也改成达梦的支持方式。 - ORM 框架适配:MyBatis 的
LIMIT分页插件、MyBatis-Plus 的分页插件默认生成的 SQL 是为 MySQL 设计的,迁移后如果不调整数据库类型配置,分页会直接报错。MyBatis-Plus 里有dbType配置,要改成DM或者ORACLE(视版本而定)。另外如果项目里大量用到了 MyBatis 的<script>动态 SQL,也要全量回归。
3.6 数据校验:迁移后的死守环节
迁移完不等于验收完,数据校验是最后一道防线,也是一定不能省的一步。
我的校验策略分三层:
- 数量校验:每张表
COUNT(*)两边对比。注意 MYISAM 和 INNODB 的 COUNT 行为差异,最好都用COUNT(主键)来对比。 - 抽样校验:每张表随机抽几百条记录,逐字段对比值。重点看字符串、日期类型,有没有乱码、有没有精度丢失。
- 业务级校验:跑几遍核心业务的完整链路(比如下单→支付→对账),确认业务数据在迁移后能正确流转。
第三层最容易漏,但价值最高。很多隐蔽问题藏在对 NULL 的处理、对时间的格式化、对自增 ID 的依赖上,这些光靠数据对比查不出来。
4. 常见问题与排查技巧实录
4.1 高频报错速查表
把我在多个迁移项目里遇到的报错信息整理成一个速查表,排查时可以对着查:
| 报错信息 | 根本原因 | 处理方式 |
|---|---|---|
| 字符串长度超出限制 | VARCHAR 长度语义差异 | 建库时设 LENGTH_IN_CHAR=1,或调整字段长度 |
| 无效的关系名 / 表不存在 | 标识符大小写敏感,双引号包裹了小写名字 | 统一使用大写不加引号引用对象 |
| self-increasing column 不允许显式插入 | IDENTITY 列限制 | 去掉自增列的显式插入值,或临时启用 IDENTITY_INSERT |
| 第 N 行附近出现错误: 语法错误 | 反引号/函数/LIMIT 语法不兼容 | 定位具体语句,按兼容语法改写 |
| 无效的列名 "rownum" | 达梦 ROWNUM 是关键字 | 列名不要用 rownum,或加引号(不建议) |
| 数据类型不匹配 | DATE_FORMAT 返回字符串,目标列是日期类型 | 增加 TO_DATE/TO_CHAR 做类型转换 |
| 无法解析的日期时间字符串 | 格式串用错 | 将 %Y-%m-%d 改成 YYYY-MM-DD |
| 锁等待超时 | 达梦事务隔离和锁行为不同 | 检查事务是否没提交,调整事务粒度 |
4.2 一个完整的"语法排错"实战过程
举个例子。迁移一个订单分页查询功能时,应用报了一个诡异的语法错误。原始 SQL 长这样:
SELECT * FROM orders WHERE status = '1' ORDER BY create_time DESC LIMIT 0, 15问题是:这张 SQL 在 MySQL 里跑了三年都很稳,到了达梦死活报错。排查过程如下:
第一步,拿到完整报错信息。达梦提示第 1 行附近出现错误: 语法错误,这种报错非常粗,只告诉你语法不对,定位不到具体哪个词,需要自己拆 SQL。
第二步,逐段验证。把 SQL 砍掉LIMIT执行,发现能跑通。说明问题出在分页部分。再测试LIMIT 0, 15的写法时发现达梦不认,换LIMIT 15 OFFSET 0就能跑。这就是典型的方言差异。MySQL 的LIMIT offset, count(偏移量在前)和达梦的LIMIT count OFFSET offset(偏移量在后)正好是反的,报错也很正常。改写后:
SELECT * FROM orders WHERE status = '1' ORDER BY create_time DESC LIMIT 15 OFFSET 0第三步,发现还有隐患。这个 SQL 里ORDER BY create_time如果 create_time 有的是 NULL,达梦在降序时 NULL 排行为和 MySQL 不同。虽然这个例子中 create_time 有默认值,但生产环境还是要确认一下。
这个排查过程本身没什么秘诀,核心方法是:拆分语句、逐段验证、确认行为一致。不要试图一次性看懂一大段复杂 SQL。
4.3 连接工具和调试建议
达梦的图形化管理工具(DM 管理工具)日常调试够用,但某些场景不如命令行的 disql 明确。我迁移时的调试习惯是:
- 先用 DM 管理工具逐条执行有问题的 SQL,观察报错。
- 遇到疑难语法,用 disql 配合
SET ECHO ON看详细执行过程。 - 所有改写后的 SQL,在达梦的执行计划里过一眼,避免迁移后性能严重滑坡。
还有一个建议:如果用的是 Navicat 连接达梦数据库(很多人有这个习惯),Navicat 对达梦的支持是有限的,收费版本也不一定支持所有达梦功能,遇到诡异问题时换官方工具试一下,排除工具本身的坑。
4.4 容易被忽略的字符集与乱码问题
MySQL 迁移到达梦,乱码问题集中在两类场景:
第一类是迁移过程乱码。DTS 迁移时源库和目标库的字符集不一致,导出来的数据直接变问号。处理方式是:迁移前确认 MySQL 的字符集(通常是 utf8mb4),目标达梦建库时也选 UTF-8,两边对齐后再跑 DTS。达梦的字符集在建库时选定,后期想改非常麻烦,这里务必一次到位。
第二类是应用连接乱码。JDBC 连接串中设置字符集参数的方式和 MySQL 不一样,达梦要用characterEncoding=UTF-8之类的参数。如果应用连接后查询中文正常但写入中文变问号,重点查应用层连接串的编码参数和表的列字符集。
4.5 注意 MySQL 独有特性在达梦里"静默失效"的情况
迁移过程中我比较警惕的是那种不报错但行为不同的情况,比直接报错更隐蔽。举几个实际例子:
- MySQL 的
GROUP BY在ONLY_FULL_GROUP_BY关闭时允许查询非聚合列,达梦在兼容模式下也可能允许,但返回的行可能和 MySQL 不一致。上线前必须把这类 SQL 手工改造成标准聚合写法。 - MySQL 的
ORDER BY可以用列别名,达梦在部分场景下也支持,但复杂查询(尤其含 UNION 的子查询)中对别名的处理不一样。 - MySQL 的
CREATE TABLE IF NOT EXISTS达梦有对应语法支持,但ALTER TABLE ADD COLUMN IF NOT EXISTS这种写法达梦可能不认。
排查这些问题的方法没有捷径,就是把核心业务 SQL 在两边各跑一遍,比对结果集。迁移项目里最重要的不是"SQL 能执行",而是"SQL 执行的结果和源库一致"。
5. 一些迁移后性能调优的看法
语法跑通只是第一步,迁移后如果性能掉链子,业务方一样会找上门。
5.1 执行计划对比
同一套数据,同一句 SQL,MySQL 和达梦生成的执行计划可能完全不同。迁移后的第一轮性能排查,建议直接看达梦的执行计划,找全表扫描和排序操作。尤其注意:
- MySQL 惯用的索引
(a, b)在达梦里如果查询条件变了,索引可能失效。 - 达梦的统计信息在迁移后默认是空的,需要跑一遍
DBMS_STATS.GATHER_SCHEMA_STATS之类的统计信息收集操作,否则优化器可能选出糟糕的执行计划。这一点很关键,刚迁移完性能差往往不是 SQL 的问题,而是统计信息没更新。 - 达梦的并行度配置(如
PARALLEL)没有默认开启,大表查询要手工调。
5.2 索引重建与维护
DTS 迁移表结构时可能会把索引一起搬过来,但搬过来的索引不一定是达梦的最佳形态。我的习惯是:迁移完成后,把每张表的索引全部列出来,和 MySQL 侧的索引做一个对比清单,去掉冗余索引、补齐缺失索引,按达梦的索引命名规则重命名。
索引方面还有一个坑:达梦的降序索引和 MySQL 写法不同,MySQL 的INDEX(a DESC)达梦不直接支持(要看版本),需要调整或改用函数索引。如果你依赖降序索引优化顺序查询,迁移后要特别测试。
5.3 锁相关行为差异
MySQL 和达梦在锁方面的行为差异,往往是上线后才暴露的问题。
MySQL 的默认隔离级别是可重复读,InnoDB 使用行锁和间隙锁的组合。达梦默认支持多种隔离级别,但并发控制实现细节不同。有几点实测体会:
SELECT ... FOR UPDATE在 MySQL 里对无索引条件的查询可能会锁大量行,达梦的锁行为也有类似情况,但锁等待的报错和超时机制不一样,应用里如果统一捕获了"锁等待超时"的异常类型,迁移后这个捕获可能失效。- MySQL 的
INSERT ... ON DUPLICATE KEY UPDATE在冲突时会额外更新一条记录,达梦对应实现的加锁范围可能更大,并发高的场景下更容易出现锁等待。 - 如果应用里有大事务,迁移到达梦后事务长度和锁持有时长对整体吞吐量的影响可能需要调参,比如调整锁等待超时参数
LOCK_TIMEOUT。
这些问题的排查思路是先压测、再对比、最后调优。上线前一定做一轮并发写入压测,看看在达梦下面的锁等待情况。
6. 写在最后的一些个人体会
回头看这几个迁移项目,我最大的体会是:"迁移"不是一个技术动作,而是一个系统工程。SQL 语法问题只是浮在水面上的冰山一角,水面之下是数据类型的语义差异、NULL 和空字符串的哲学区别、事务和锁的行为差异、字符集的血泪教训。
所以如果你正准备做 MySQL 到达梦的迁移,我有几句实在话:
第一,别指望一把梭。无论 DTS 工具多智能、兼容模式多强大,手工核对和改造都是逃不掉的。把工作量和时间预算打足,尤其是存储过程和应用 SQL 的回归测试。
第二,尽早拉通应用开发团队。数据库迁移不是 DBA 一个人的事,应用代码里的 SQL 才是大头。让开发团队提前介入,了解达梦的方言习惯,可以减少大量低效沟通。
第三,测试环境一定要模拟生产数据量。空库和只有几千行的库跑不出真实问题,字符串超长、性能退化、锁冲突这些问题,往往都是上了几百万行数据之后才暴露。
第四,建立完善的比对机制。每次 SQL 改写之后,都要有对应的回归验证。我习惯维护一份"SQL 转换对照表",把 MySQL 原文、达梦改文、验证结果、踩坑记录都存下来。这个表不仅是当前项目的交付物,也是后续团队接手和维护的宝贵资料。
迁移达梦这件事,说难不难,说简单也确实繁琐。只要掌握好语法差异的关键点、用对迁移工具、把数据校验做实,整个过程是完全可以顺利拿下来的。希望这篇整理能帮正在迁移的朋友少走一些弯路,尤其是那些在报错信息面前摸不着头脑的时刻,能想起这里还有一个速查表可以看。