☰
MySQL迁移达梦数据库:SQL语法差异与实战改写方案
2026/10/19 12:15:53 网站建设 项目流程

前阵子接手了一个挺典型的迁移任务,把一套跑了好几年的 MySQL 业务库整体迁到达梦数据库。说实话,动手之前我也以为这就是个"导出导入"的活儿:源库导出、目标库导入、改一下连接串,最多再调两三个不兼容的函数。真正做起来才发现,坑基本都埋在 SQL 语法这一层——同样的逻辑,MySQL 写得顺手的写法,到达梦那边就是一堆报错。标题里这几个关键词:mysql、迁移达梦数据库、sql 语法问题、迁移方案,完全就是我这两三个月的工作总结。这篇文章适合正在做国产数据库替换、或准备把 MySQL 迁到达梦的开发和 DBA 看,里面没有理论空话,全是能直接照着改的东西。

1. 迁移前先想清楚:别一上来就动 SQL

很多人拿到迁移需求,第一时间就是找个工具开始导数据,结果导到一半发现表结构不对、SQL 到处报错,再回头补课,返工成本特别高。我这次的做法是先花两三天把两边的情况摸清楚,再定迁移方案,后面执行起来反而快。

1.1 盘点源库"家底",决定迁移难度

第一步不是连上达梦,而是把 MySQL 侧的现状彻底盘一遍。建议至少收集这么几项:

  • 表数量、单表数据量排行、总数据量,估算迁移窗口和存储空间。
  • 存储过程、函数、触发器、事件(Event)的数量和规模。
  • 外键约束、自增列、唯一索引的使用情况。
  • 特殊数据类型的使用情况,比如 JSON、GIS、ENUM、SET。
  • 业务 SQL 里高频出现的函数和写法,比如 GROUP_CONCAT、DATE_FORMAT、LIMIT、ON DUPLICATE KEY UPDATE。

我这次统计下来是 200 多张表,数据量加起来两三个 TB,存储过程 30 多个,函数和触发器少量,JSON 字段有几十个。这个量级决定了不可能靠纯手工一条条 SQL 去迁移,必须工具牵头,但工具的转换能力又有限,所以我在方案里同时准备了手工脚本做兜底。

迁移难度主要就看三件事:有没有大量存储过程、有没有特殊数据类型、有没有写死在 SQL 里的方言函数。这三样占得越少,迁移越接近体力活;占得越多,排期就要越宽松。尤其是存储过程,MySQL 和达梦的 PL/SQL 语法差异非常大,指望工具自动翻译不现实,这块在计划里就要预留人工改写的周期。

1.2 达梦侧先把三个参数定死,后面少踩一半坑

达梦数据库有几个初始化参数会影响后面 SQL 的兼容程度,最好在建库之前就定好,因为有些参数初始化后很难改。

第一个是大小写敏感参数。达梦默认对标识符区分大小写,而 MySQL 在默认配置下对表名不敏感。这直接导致一种很常见的报错:MySQL 里写的SELECT * FROM sys_config可能没问题,到了达梦就报"无效的表名"。如果业务系统允许,初始化达梦实例时把大小写不敏感打开,SQL 迁移的工作量能降一大截。但这个参数最好在初始化实例时确认,后面想改要动元数据,风险不小。

第二个是兼容模式。达梦提供兼容 Oracle、MySQL、SQL Server 等不同语法的模式,设置之后部分 MySQL 特有写法能少改一些。但我实际测试下来的经验是:不要迷信兼容模式,它救不了所有场景,尤其是函数和特殊 DML 语法,最终还得靠人工改写。可以把兼容模式当成"减少报错噪音"的手段,而不是"一劳永逸"的方案。

第三个是字符集。MySQL 这边普遍是 utf8mb4,达梦初始化时要把字符集对齐,不然中文、表情符号导过去乱码,后面排查起来非常痛苦。另外排序规则也要注意,MySQL 的utf8mb4_0900_ai_ci这类排序规则在达梦里没有对应项,迁移后排序结果可能有细微差异,涉及中文排序的业务要重点回归。

2. SQL 语法差异,我踩过的重灾区

这一节是整篇文章的核心。达梦和 MySQL 都叫"关系型数据库",但实际方言差异比想象中大得多。我按踩坑频率从高到低,把最典型的几类问题列出来,每一项都给出可落地的改写方案。

2.1 数据类型映射:看上去一样,实际不是一回事

迁移工具会自动做一部分类型映射,但映射结果经常不理想。我整理了一份实际用到的映射表,照着改基本不会错:

MySQL 类型达梦类型注意事项
tinyint(1)TINYINT达梦没有 BOOLEAN 类型,JDBC 读出来是 short,应用层要适配
int unsignedINT / BIGINTUNSIGNED 无对应类型,超范围数据要改 BIGINT
varchar(n)VARCHAR(n)字符长度语义有差异,建议按实际长度加 20% 余量
datetimeTIMESTAMP行为接近,但精度和默认值要验证
timestampTIMESTAMP时区处理不同,应用层要统一
text / longtextTEXT / CLOB大部分函数不能直接用,要 CAST
blob / longblobBLOB问题不大,注意驱动读取方式
decimal(p,s)NUMERIC(p,s)基本一致,注意 ROUND 舍入规则差异
jsonTEXT / CLOB没有原生 JSON 类型,JSON 函数基本全不能直接用
enum / setVARCHAR + CHECK建议应用层改成普通字符串 + 校验

我这次最头疼的是 JSON 字段。MySQL 里一个字段存了一堆 JSON,还经常用JSON_EXTRACT、JSON_CONTAINS在 SQL 里直接取数,到达梦后这些函数全没有。最后的处理方式是把 JSON 改成 TEXT 存储,查询逻辑全部挪到应用层用代码解析。这个改造涉及不少 SQL,建议迁移前就统计好 JSON 相关函数的使用面积,早点跟业务确认改造范围。

2.2 分页写法:LIMIT 不背锅,但也不省心

MySQL 的分页几乎清一色是LIMIT offset, count,达梦虽然也支持 LIMIT,但我在实际迁移中发现有几个场景很容易踩雷:一是 LIMIT 后面带复杂的表达式或变量,二是嵌套视图里用 LIMIT 再包一层排序,三是 LIMIT 和聚合函数、窗口函数一起用时,偶发执行计划异常。

稳妥的做法是统一改写成 ROWNUM 方案。举个例子,MySQL 这条 SQL:

SELECT * FROM t_order ORDER BY id DESC LIMIT 10, 20;

达梦可以改写成:

SELECT * FROM ( SELECT t.*, ROWNUM rn FROM ( SELECT * FROM t_order ORDER BY id DESC ) t WHERE ROWNUM <= 30 ) WHERE rn > 10;

虽然看着啰嗦,但执行计划稳定,各种子查询场景都能兜住。如果用的 ORM 框架自带分页插件,比如 MyBatis-Plus,可以直接配置达梦方言,让插件自动生成兼容的分页 SQL,省去手工改写的体力活。

2.3 函数替换:照着清单抄就行

函数差异是迁移中最大的工作量来源。我整理了一张高频函数替换对照表,几乎每个 MySQL 迁移项目都会用到:

MySQL 函数达梦替代写法说明
IFNULL(a, b)NVL(a, b)达梦也有 IFNULL,但建议统一用 NVL
IF(expr, a, b)CASE WHEN expr THEN a ELSE b END达梦没有 IF 函数
GROUP_CONCAT(x SEPARATOR ',')LISTAGG(x, ',')注意长度限制和排序问题
DATE_FORMAT(t, '%Y-%m-%d')TO_CHAR(t, 'YYYY-MM-DD')格式串写法完全不一样
UNIX_TIMESTAMP()DATEDIFF(s, '1970-01-01 08:00:00', SYSDATE)建议应用层处理
SUBSTRING_INDEXREGEXP_SUBSTR + INSTR 组合没有同名函数
FIND_IN_SET(x, 'a,b,c')改写成 IN 或 LIKE 边界匹配没有同名函数
CONCAT(a, b) 单参数CAST(a AS VARCHAR)MySQL 允许单参数,达梦标准模式会报错
NOW()SYSDATE / CURRENT_TIMESTAMP基本可兼容
DATE_ADD / DATE_SUBADD_MONTHS 或 直接 + INTERVAL语法有差异,要验证

这里特别说一下 GROUP_CONCAT。MySQL 的GROUP_CONCAT默认有长度限制,超长会静默截断,迁到达梦后我用LISTAGG替代,但遇到结果超过 VARCHAR 上限时会直接报错。我这边最后是把聚合列强制转成 CLOB,或者在应用层先分组再拼接,彻底绕开数据库函数。

DATE_FORMAT 也得重点检查。MySQL 的格式串是%Y-%m-%d %H:%i:%s这种,达梦的 TO_CHAR 是YYYY-MM-DD HH24:MI:SS,如果只是简单替换函数名不换格式串,导出来的日期全是错的。这个靠人眼找不现实,最好写个脚本扫描代码仓库里的 SQL,把DATE_FORMAT出现的位置全部标出来,逐个改写。

2.4 自增列、默认值和时间戳的坑

MySQL 建表最常见的写法是:

CREATE TABLE t_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );

达梦里自增列要用 IDENTITY 实现,写法是:

CREATE TABLE t_user ( id BIGINT IDENTITY(1,1) PRIMARY KEY, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP );

这里最大的坑是ON UPDATE CURRENT_TIMESTAMP。MySQL 会在行更新时自动维护更新时间,达梦没有这个语义,如果表结构里依赖这个特性,迁移后更新时间就不会自动变了。解决办法有两个:一是应用层在 UPDATE 时显式赋值update_time = CURRENT_TIMESTAMP;二是在达梦上建一个 BEFORE UPDATE 触发器自动赋值。我建议优先改应用层,因为触发器多了会影响写入性能,而且容易埋坑。

还有一个坑是 TIMESTAMP 的默认行为。MySQL 的 TIMESTAMP 在旧版本里默认允许为空且自动初始化为当前时间,达梦不会这样处理,建表时最好把所有时间字段的 DEFAULT 和 NOT NULL 属性都显式写清楚,别依赖数据库的隐式行为。

2.5 DDL 语句逐个适配:IF NOT EXISTS 这类"小甜点"请忘掉

MySQL 的 DDL 写起来非常随意,CREATE TABLE IF NOT EXISTS、DROP TABLE IF EXISTS、ALTER TABLE ... MODIFY COLUMN、RENAME TABLE、COMMENT 'xxx'全是顺手就写。到了达梦,这些写法大多不支持,需要逐个适配:

  • CREATE TABLE IF NOT EXISTS:达梦不直接支持,要先查系统表判断是否存在,再决定是否建表。
  • DROP TABLE IF EXISTS:同理,先查 USER_TABLES 再 DROP。
  • ALTER TABLE t MODIFY COLUMN c VARCHAR(100):达梦的语法是ALTER TABLE t ALTER COLUMN c SET DATA TYPE VARCHAR(100)。
  • RENAME TABLE t1 TO t2:达梦用ALTER TABLE t1 RENAME TO t2。
  • COMMENT '注释'写在列定义后面:达梦要单独执行COMMENT ON COLUMN 表.列 IS '注释'。

这些差异看着简单,但数据字典、报表系统、定时任务里到处都是,批量改起来很费时间。建议迁移前用脚本把所有 DDL 语句统一导出,做一次全量语法检查,别等迁移执行到一半才发现。

2.6 特殊 DML 写法:ON DUPLICATE KEY UPDATE 和 INSERT IGNORE

MySQL 的ON DUPLICATE KEY UPDATE和INSERT IGNORE在业务里用得非常频繁,达梦没有这两个语法。最通用的替代方案是 MERGE,达梦对 MERGE 的支持比较完善。

比如 MySQL 这条:

INSERT INTO t_config(k, v) VALUES('key1', 'val1') ON DUPLICATE KEY UPDATE v = 'val1';

达梦可以改写成:

MERGE INTO t_config t USING (SELECT 'key1' AS k, 'val1' AS v FROM DUAL) s ON (t.k = s.k) WHEN MATCHED THEN UPDATE SET t.v = s.v WHEN NOT MATCHED THEN INSERT (k, v) VALUES (s.k, s.v);

这个改写要注意唯一键的含义必须完全一致,如果表的唯一约束来自多个列组合,ON条件也要跟着换成多列等值匹配。另外,如果 SQL 里同时依赖自增列的返回值,MERGE 的处理方式跟 MySQL 的LAST_INSERT_ID()不同,需要在应用层单独处理。

3. 迁移实操流程:关键步骤怎么做

方案设计完之后,就是落地执行。我这次执行的流程分四步:工具迁移结构、数据装载、应用层改造、数据校验。每一步都有一些值得说的细节。

3.1 工具迁移为主、手工脚本兜底

达梦自带的迁移工具(DMS 工具集里的数据迁移功能)可以从 MySQL 直连读取元数据和数据,自动建表并导数据。它的优点是省事,缺点是遇到不兼容的类型和语法会直接报错,而且报错信息经常看不懂。我的用法是:先用工具把表结构和大部分数据导过去,导完之后手工检查系统表,把工具没建对的地方修掉。

对于数据量特别大、工具迁移效率低的场景,可以换一种路线:用 SQL 把 MySQL 数据导出成 CSV 文本,再通过达梦的高速数据装载工具导入,速度比逐条 INSERT 快好几个量级。导出 CSV 时注意字段分隔符别用逗号,业务数据里很容易出现逗号,我习惯用不可见字符做分隔符,导入时再指定同样的分隔符。

3.2 表结构与数据的迁移细节

导表结构不要直接执行工具生成的CREATE TABLE,先做一次预处理:

  • 把所有AUTO_INCREMENT改成IDENTITY(1,1)。
  • 把所有COMMENT从列定义里拆出来,单独生成COMMENT ON语句。
  • 把所有CURRENT_TIMESTAMP默认值的格式统一成达梦支持的写法。
  • 把所有JSON、ENUM、SET类型按映射表预处理。

数据迁移阶段有几个经验:

第一,导入顺序非常重要。有外键关系的表,先导主表再导子表,否则外键校验会拖慢速度还是小事,报错才是真头疼。工具迁移时可以先把外键禁用,导完数据再重建外键。

第二,大表不要一条条 INSERT。MySQL 侧的 SELECT 加上达梦侧的批量绑定,每次提交几百到几千行,整体速度能快很多。达梦对大批量 INSERT 的提交频率很敏感,跑一小批就 COMMIT 一次反而慢。

第三,日期字符串要提前统一。MySQL 对'2024-01-01 00:00:00'这样的字符串转日期很宽容,达梦则要求显式转换,建议在导出 CSV 时就把日期列统一格式化成YYYY-MM-DD HH24:MI:SS。

3.3 应用层改造清单

数据迁完,应用层不改照样跑不起来。连接配置是最基本的改动:

# MySQL 原配置 driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true&characterEncoding=utf8 # 达梦配置 driverClassName=dm.jdbc.driver.DmDriver url=jdbc:dm://127.0.0.1:5236?schema=TESTDB

连接池的检测 SQL 也要改。MySQL 常用SELECT 1,达梦可以写SELECT 1 FROM DUAL,兼容性最好。

然后是 ORM 框架。MyBatis 里如果大量使用${}拼接 SQL,分页和特殊函数基本逃不掉要改;如果用了 MyBatis-Plus,分页插件要指定数据库类型为 DM,插件才能生成正确的分页 SQL。还有一点容易被忽略:MySQL 的 SQL 里经常用反引号包字段名,达梦标准模式下不认反引号,要么全局去掉,要么改成达梦的双引号。这个要写进代码扫描规则里。

4. 迁移之后的事:校验、上线与回退

数据导完、应用能连上,不等于迁移完成。我这次在正式切换前花了一周做校验和回归,期间发现的问题比迁移过程中还多。

4.1 数据校验不能只数行数

很多人校验时就SELECT COUNT(*) FROM 两个库比一下,这个远远不够。我建议做三层校验:

第一层,行数校验。每张表都比对行数,发现不一致的优先排查。

第二层,关键字段抽样校验。比如按主键取模,抽出 1%~5% 的行,比对核心业务字段的值是否一致。对于金额、数量这类重要字段,可以直接用 SUM 比对总量。

第三层,业务口径验证。找个熟悉业务的同事,把典型查询在两个库各跑一遍,比对结果集。这一步能发现很多"数据没丢但口径变了"的问题,尤其是 NULL 和空字符串、大小写、排序规则导致的差异。

4.2 功能回归与上线切换节奏

功能回归测试要覆盖所有读写路径,特别是分页、排序、聚合统计、批量插入、定时任务这几类。分页最常见的问题是第 2 页之后数据重复或丢失,排序最常见的问题是中文排序顺序变了,聚合统计最容易踩空字符串和 NULL 的坑。

上线切换我采用了"先只读后读写"的方式:先把应用切到只读模式,让达梦库接收一部分查询流量,观察一段时间,确认没有异常后再开放写入。开放写入后至少观察一个业务周期,看有没有慢 SQL 或数据异常。

4.3 回退方案怎么留

数据库迁移必须有回退预案。我的做法是:源 MySQL 库全程保留,binlog 日志持续保留到切换后一周;应用侧做一个数据源开关,连接串可以一键切回 MySQL。

这里要提醒一点:不要想着"两边数据同步、随时互切",MySQL 和达梦的数据同步工具链并不成熟,双写方案成本很高。稳妥的思路是,迁移初期以"源库保留、目标库独立"为主,一旦确认达梦稳定运行,再逐步废弃源库。

5. 常见报错速查与三个最值得警惕的坑

最后把我实际遇到的高频报错整理成速查表,再单独说三个我觉得最容易让人栽跟头的坑。

5.1 常见报错速查表

报错信息常见原因解决办法
无效的表名 / 无效的列名大小写敏感导致标识符找不到统一大小写,或初始化时关闭大小写敏感
未找到函数 DATE_FORMAT函数不兼容改成 TO_CHAR,并调整格式串
GROUP_CONCAT 不存在函数不兼容改成 LISTAGG,注意长度
字符串超出长度VARCHAR 长度语义差异按字符长度重新估算并扩长
分页结果重复/丢失LIMIT 改写不完整统一改成 ROWNUM 写法
时间字段变成乱码/00:00:00日期字符串未显式转换SQL 中显式使用 TO_DATE / TO_TIMESTAMP
批量插入非常慢提交频率不合理加大批量绑定,批量提交
ON DUPLICATE KEY 语法错误达梦不支持改写成 MERGE

5.2 GROUP_CONCAT 截断与长度限制

MySQL 的GROUP_CONCAT有默认长度限制,超了会静默截断,所以很多老系统里这个函数的结果已经是被截过的,大家也习惯了。迁到达梦用LISTAGG后,超长会直接报错,等于把以前隐藏的问题暴露出来。遇到这种情况,先确认业务到底需要多长的聚合结果,然后要么把字段改成 CLOB,要么在应用层分组拼接。千万别简单地把group_concat_max_len调大就完事,达梦侧 VARCHAR 上限受页大小影响,有硬性天花板。

5.3 空字符串与 NULL 的语义差异

MySQL 在老版本里对空字符串和 NULL 的区分比较随意,很多老 SQL 写着WHERE phone != '',实际把 NULL 也排除掉了,但当时没人觉得有问题。达梦严格区分空字符串和 NULL,同样的 SQL 迁移后结果可能不一样。这种差异特别隐蔽,行数对得上,但统计报表里的数字就是不对。排查方法是把 WHERE 条件里所有涉及字符串字段的判定都检查一遍,明确 NULL 的过滤意图,必要时统一写成IS NULL OR col = ''。

5.4 存储过程和定时任务别拖到最后

我这次差点在存储过程上翻车。MySQL 的存储过程和达梦的语法差异非常大,变量声明、游标、异常处理、动态 SQL 的写法全都不一样,工具只能转个大概,核心逻辑几乎要重写。如果项目里存储过程多,建议迁移一开始就安排专人处理,不要等表和数据都迁完了再动手。

定时任务也是同理。MySQL 的 Event Scheduler 到达梦后没有对应概念,要么改造到达梦的作业系统里,要么直接下沉到应用层定时任务。这个涉及运维习惯的变更,提前跟运维确认,别上线前一天才发现定时任务没跑。

最后,说点个人体会。这一趟迁移下来,我最深的感受是:数据库迁移表面上考验的是工具和脚本,实际上考验的是对业务 SQL 的掌控力。MySQL 写得越随意,迁移就越痛苦。如果从头再来一次,我会在迁移前把所有 SQL 集中做一次静态扫描,把 LIMIT、GROUP_CONCAT、DATE_FORMAT、IFNULL、反引号、ON DUPLICATE KEY 这些关键字全部标出来,先改代码再动数据,顺序绝对不能反。迁移工具能帮你搬运数据,但搬不动方言和习惯。早点把 SQL 兼容性清单做出来,比到时候一条条排错效率高得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询