
做了这么多年的数据迁移我越来越觉得这个活儿的关键不在于“搬”的动作而在于搬之前的规划和搬之后的验证。尤其是把MySQL迁移到达梦这种国产数据库表面上看起来都是关系型真动起手来到处都是差异。这篇文章不整虚的就用实际迁移经验把从评估、改造、迁移到验证的完整链路拆给你看照着做能少踩一大半坑。先给这篇内容定个位适合正在做数据库国产化替代的DBA、后端开发以及项目负责人。你会看到数据类型怎么映射、SQL怎么写才不报错、存储过程怎么改才编译得过、大批量数据怎么导才快以及那些报错信息背后真正的原因。我尽量把每一步的“为什么”也讲清楚毕竟知其然才能在自己遇到变体问题时举一反三。1. 迁移前的全局评估与目标定位很多人拿到迁移任务第一反应就是找个工具点几下把表和数据导过去。这个思路不是不行而是容易在后期吃大亏。工具能帮你搬走“形”搬不走“魂”——那些藏在存储过程里的业务逻辑、隐式类型转换、特殊函数用法才是真正的坑。1.1 先盘清楚要迁什么我建议开工之前先做一个对象清单盘点把源库里的东西分门别类列出来至少包括这几类表结构表数量、字段数量、索引、约束、自增列、默认值、字符集。数据总行数、大表清单、单表数据量级、是否有TEXT/BLOB大字段。逻辑对象视图、存储过程、函数、触发器、事件MySQL的EVENT。外部依赖应用侧SQL中有没有非标准写法、ORM框架生成的SQL是否带方言特性。这一步的作用是让你对工作量有个准确判断也方便后续分批次推进。我见过一个项目表面上只有几十张表结果一张核心表里全是TEXT字段单表几个GB导入时如果不做特殊处理能跑到怀疑人生。提前把这类“硬骨头”标出来时间分配就不会失控。盘完对象之后还要做一次兼容性抽查。不要等到全量迁移完再测先挑三五张有代表性的表把DDL拉出来比对把几条复杂SQL拿去目标库跑一下成本低、见效快能提前暴露大部分方向性问题。1.2 方案选型图形工具、脚本迁移怎么选迁移方案一般有三条路各有利弊没有绝对的好坏关键看场景。第一条路是用达梦自带的图形迁移工具DTS。它的优势是操作门槛低能把表结构、数据、视图这些“常规对象”一次性带过去适合时间紧、对象规整、逻辑对象不复杂的项目。但它的短板也很明显对存储过程这种高兼容性要求的对象迁移后经常需要手工改而且日志信息有限出了问题不太好定位。第二条路是手工编写DDL脚本和导入脚本。听着原始但可控性最强。你可以对每个字段做精确的类型映射对每个存储过程做逐行改写还能结合版本管理工具把整个迁移过程脚本化方便重复执行和复盘。代价就是前期工作量偏大对实施人员的要求也更高。第三条路是借助ETL工具做数据层面的同步比如用通用的数据集成平台。这种方式对异构数据库的大数据量同步比较友好但通常解决不了存储过程、函数这类代码对象的迁移只能作为数据搬运的辅助手段。我个人的建议是组合使用表结构和数据用DTS做第一轮快速搬运再用脚本做第二轮修正和补充逻辑对象全部手工处理。这样既有速度又有质量兜底。1.3 目标端环境规划要提前定环境规划这件事看似不起眼实际上决定了后面所有步骤的体验。关键决策点有三个。第一个是数据库初始化时的兼容模式。达梦支持兼容多种数据库语法如果源头是MySQL建议在初始化实例或建库时开启MySQL兼容模式。这样做的好处是部分函数和语法能被直接接受比如字符串处理、日期函数等。但你要清醒兼容模式解决的只是“部分语法”问题不是万能药复杂逻辑该改写还得改写。第二个是字符集和大小写敏感性。MySQL的utf8mb4字符集、大小写敏感的排序规则和达梦的默认设置不一定一致。如果在源头就是utf8mb4目标库建议也用UTF8类字符集避免中文乱码和排序错乱。大小写敏感性更要提前定死因为这是初始化级别的参数后期想改非常麻烦。第三个是表空间和用户规划。达梦里用户和模式是绑定的一个用户对应一个同名模式这和MySQL的DATABASE概念不同。建议一个MySQL库对应达梦的一个用户/模式应用连接就用这个用户这样权限隔离和对象归属都清晰。2. 两库差异全梳理这是迁移成败的核心如果说评估规划是“战前侦察”那差异梳理就是“战术手册”。MySQL和达梦虽然都是关系型数据库但底层设计思路不同导致在数据类型、SQL语法、过程化语言三个方面差异非常大。这一节把常见的差异点全部摊开建议收藏当字典用。2.1 数据类型映射一张表搞定绝大多数建表问题建表是迁移的起点数据类型映射错了后面全得返工。下面是常见MySQL类型到达梦的标准映射方式MySQL类型达梦推荐类型说明TINYINTTINYINT取值范围一致直接映射TINYINT UNSIGNEDSMALLINT达梦无UNSIGNED概念需扩展长度SMALLINT / INT / BIGINTSMALLINT / INT / BIGINT直接映射注意UNSIGNED处理VARCHAR(n)VARCHAR(n)注意长度单位差异见下方说明CHAR(n)CHAR(n)直接映射TEXT / MEDIUMTEXTTEXT / CLOB大字段建议用CLOB操作更灵活LONGTEXTCLOB防止超长文本截断BLOB / LONGBLOBBLOB二进制大对象直接映射DATETIMEDATETIME直接映射TIMESTAMPTIMESTAMP注意默认值和时区行为差异DATE / TIMEDATE / TIME直接映射DECIMAL(m,n)DECIMAL(m,n)直接映射FLOAT / DOUBLEFLOAT / DOUBLE直接映射注意精度表现BITBIT直接映射ENUMVARCHAR CHECK达梦不支持ENUM用约束兜底SETVARCHAR建议应用层校验达梦无SETJSONTEXT / CLOB达梦有JSON支持但不通用稳妥用CLOB这里最需要留意的是VARCHAR的长度单位。MySQL的VARCHAR(n)严格说是字符数而达梦的VARCHAR长度单位存在字节和字符两种口径取决于初始化参数设置。如果在UTF8字符集下一个汉字在MySQL里算1个字符到达梦按字节算就是3个字节。原表VARCHAR(100)能存100个汉字达梦如果按字节可能只能存33个。这会导致两个问题一是数据导入时报“字符串长度超出”二是同样的数据存储容量下降。规避方法是在建库时确认长度单位口径或是在生成DDL时按业务需求放大长度。ENUM和JSON这两个类型要单独说。ENUM在MySQL里是个省事的东西但达梦没有对应类型最稳的做法是建成VARCHAR并加CHECK约束由数据库保证取值合法。JSON类型也一样除非你的达梦版本明确支持JSON功能否则一律用CLOB存原始JSON字符串序列化和解析交给应用层。牺牲一点查询便利换来的是一劳永逸的兼容性。2.2 SQL语法差异与改写清单数据类型是“表”的层面SQL语法则是“查询”的层面。应用的绝大部分SQL都要经过这关改写量常常是最大的。我挑几个必踩的点讲。分页语法是最高频的差异。MySQL的LIMIT m,n写法到达梦这里行不通达梦支持的是TOP、ROWNUM以及标准SQL的FETCH。只取前N条时用SELECT TOP N * FROM t就行真正翻页时推荐用ROWNUM包一层子查询-- MySQL写法 SELECT * FROM t ORDER BY id LIMIT 0, 10; -- 达梦写法 SELECT * FROM ( SELECT t.*, ROWNUM rn FROM ( SELECT * FROM t ORDER BY id ) t WHERE ROWNUM 10 ) WHERE rn 1;不同的达梦版本支持的语法有细微差别有的新版本也能直接识别LIMIT但为了兼容性我建议按上面的ROWNUM写法来它在各个版本里都稳定。函数替换是第二大类。IFNULL要换成NVLDATE_FORMAT换成TO_CHAR格式串从%Y-%m-%d变成YYYY-MM-DDGROUP_CONCAT换成LISTAGG多参数CONCAT如果遇到只支持两个参数的版本要嵌套或改用||连接符。这些替换看起来小但如果应用里写了几百处逐个手工改就要命了。所以我在前面强调要先做兼容性抽查目的就是提前评估这个改写量。还有一个容易忽视的是标识符处理。MySQL习惯用反引号包裹库名和表名达梦不支持反引号需要去掉必要时改成双引号。另外MySQL表名字段名默认大小写不敏感而达梦对未加引号的标识符统一按大写存储这在多数情况下没影响但如果你应用里有大小写敏感的字符串比较就要留意。INSERT冲突处理的差异也很大。MySQL的INSERT ... ON DUPLICATE KEY UPDATE在达梦不支持需要改写为MERGEMERGE INTO t1 USING (SELECT #{id} AS id, #{name} AS name FROM DUAL) t2 ON (t1.id t2.id) WHEN MATCHED THEN UPDATE SET t1.name t2.name WHEN NOT MATCHED THEN INSERT (id, name) VALUES (t2.id, t2.name);这种改写在数据同步类业务里非常常见建议在写工具类SQL时提前统一成MERGE风格省得后来单独返工。2.3 存储过程与触发器的兼容性改造存储过程是迁移里最费神的部分因为MySQL的过程语言和达梦兼容Oracle风格在骨架上有本质差异。我总结了几个高频改造点。变量声明的位置不同。MySQL允许在BEGIN...END内部中途声明变量达梦习惯把所有变量集中声明在DECLARE区域。赋值语句也不同MySQL用SET或者SELECT INTO达梦用:赋值。举个例子一个最简单的变量逻辑-- MySQL风格 BEGIN DECLARE v_cnt INT; SELECT COUNT(*) INTO v_cnt FROM t; SET v_cnt v_cnt 1; END; -- 达梦风格 DECLARE v_cnt INT; BEGIN SELECT COUNT(*) INTO v_cnt FROM t; v_cnt : v_cnt 1; END;看着差别不大但存储过程一长这种细节会反复触发编译错误。异常处理机制完全不是一个套路。MySQL用DECLARE CONTINUE HANDLER FOR NOT FOUND来捕获游标结束达梦则用EXCEPTION块和内置异常名NO_DATA_FOUND。游标循环的写法也要相应调整达梦更常用FOR循环直接遍历结果集。自增列在存储过程中的取值方式同样要改。MySQL用LAST_INSERT_ID()拿刚插入的ID达梦一般用IDENTITY_VAL_LOCAL()或者使用序列的CURRVAL。如果源逻辑里大量依赖这个函数趁迁移时统一改成序列方案后面维护起来反而省心。这里我提一个经验性的忠告存储过程迁移不要追求逐字翻译而是先看懂原逻辑再用达梦的习惯重新写一遍。逐字翻译的产物往往又丑又难调试重写虽然费点时间但长远看可维护性高得多。3. 实操全流程逐步拆解讲完理论进入实操。我会按真实项目的推进节奏从建库到验证一步步走每一步给出可直接落地的做法。3.1 目标库、用户与模式准备第一步是在达梦里创建业务用户和模式。达梦的语法和Oracle接近创建一个用户后会自动产生同名模式。比如要迁移一个名为order_db的MySQL库到达梦就是创建order_db用户CREATE TABLESPACE order_ts DATAFILE order_ts.dbf SIZE 1024M AUTOEXTEND ON NEXT 128M; CREATE USER order_db IDENTIFIED BY your_password DEFAULT TABLESPACE order_ts; GRANT DBA TO order_db;单独建表空间再指定给用户好处是数据文件位置可控后续备份恢复都清楚。如果你不差这一步直接建用户用默认表空间也行但大表项目强烈建议规划独立表空间防止默认表空间被塞爆。还需要确认兼容参数是否已经生效。可以执行下面这类查询确认当前实例的兼容模式相关配置SELECT * FROM V$DM_INI WHERE INI_NAME LIKE %COMPATIBLE%;如果在建库时没开MySQL兼容模式而项目里又有大量MySQL方言SQL可以考虑调整对应兼容参数但要注意有些参数是静态的需要重启实例才生效。所以最好在迁移前就定好避免中途切换。3.2 表结构迁移的两种落地方式表结构迁移我建议按“先用DTS拖一遍再用脚本修”的节奏走。用DTS迁移表结构时勾选好源库连接、目标库连接、需要迁移的表工具会自动生成建表语句并执行。这个过程很快基本不用干预。但工具生成的DDL通常有几个问题数据类型映射是按默认规则来的不一定最优注释可能丢失索引命名可能不符合你的规范。所以DTS跑完后一定要抽样检查几类表含大字段的表、含自增列的表、含特殊默认值的表。对于需要手工控制的核心表我习惯直接写DDL。拿一张用户表举例-- MySQL原始表 CREATE TABLE t_user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100) DEFAULT NULL, status ENUM(ACTIVE,DISABLED) DEFAULT ACTIVE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 对应达梦表 CREATE TABLE t_user ( id INT IDENTITY(1,1) NOT NULL, username VARCHAR(200) NOT NULL, email VARCHAR(200) DEFAULT NULL, status VARCHAR(20) DEFAULT ACTIVE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT pk_t_user PRIMARY KEY (id), CONSTRAINT ck_t_user_status CHECK (status IN (ACTIVE,DISABLED)) ); CREATE INDEX idx_username ON t_user(username); COMMENT ON COLUMN t_user.username IS 用户名;这里有两个值得注意的处理。一是VARCHAR长度我按字符数估算后放大了避免因字节口径不同导致长度不够。二是ENUM改成了VARCHAR加CHECK约束业务取值合法性仍然由数据库把关。索引的命名规范也可以趁这次迁移重新梳理毕竟MySQL下经常出现一堆idx_开头的冗余索引迁移是清理的好时机。3.3 数据迁移从“能导入”到“导得快”表结构就位后开始导数据这个阶段的目标就两个字快、稳。如果表不多、数据量在几十GB以内DTS的数据迁移功能足够用。配置好源和目标连接选择对应表工具会自动批量读取写入。但如果遇到超大表直接全表SELECT再逐条INSERT会非常慢而且容易造成目标库事务日志膨胀。数据量大的时候我推荐用达梦的命令行批量装载工具dmfldr类似Oracle的SQL*Loader。先导出一份文本格式的数据文件再用dmfldr快速装载速度比逐条INSERT快一个量级。控制文件示意如下LOAD DATA INFILE t_user_data.txt INTO TABLE t_user FIELDS TERMINATED BY | TRAILING NULLCOLS ( id, username, email, status, created_at )执行装载命令时可以指定批大小和并行度比如dmfldr useridorder_db/your_password control\t_user.ctl\ data\t_user_data.txt\ batch10000 parallel4实操中有个细节如果目标表有IDENTITY自增列默认情况下不允许直接插入指定值。如果业务需要保留原ID值最常见做法是建表时不设置IDENTITY字段用普通INT数据导入后再用序列加触发器模拟自增。如果版本支持显式插入IDENTITY列也要在配置里开对应开关请根据你用的版本文档确认。千万别在导入中途才发现写不进ID那种返工很熬人。导入过程中建议分批提交一批一万到两万条比较稳妥。同时关掉目标表的约束和索引导完再重建。这样能显著缩短时间也避免约束冲突在导入中反复打断流程。等数据全部落库后再按依赖顺序重建外键和触发器。3.4 逻辑对象迁移与迁移后验证数据和表结构都完成了轮到视图、存储过程、函数、触发器这些逻辑对象。视图迁移相对简单重点检查SQL语法差异把MySQL的函数替换成达梦写法就行。存储过程和函数就需要逐个编译、逐个调试了。达梦里编译存储过程的常见方式是CREATE OR REPLACE PROCEDURE proc_name AS ...创建成功后还要用下面的语句再检查一下状态SELECT OBJECT_NAME, STATUS FROM USER_OBJECTS WHERE OBJECT_TYPE PROCEDURE;凡是STATUS不是VALID的对象都要进去看具体编译错误。达梦提供了系统视图查编译错误比如USER_ERRORS根据错误行号定位修改即可。迁移后验证是“高质量”这部分的真正体现。我建议至少做三层验证。第一层是对象数量验证比较源库和目标库的表数量、视图数量、存储过程数量确保一个不少。第二层是数据一致性验证。对每张表比对行数最笨也最可靠的办法是COUNT(*)对账SELECT COUNT(*) FROM t_user;再加一层特征值比对比如对关键数值字段做SUM对时间字段做MAX/MIN防止行数一致但内容错位。第三层是业务功能验证。把应用切到目标库连接跑一遍核心链路比如登录、下单、查询列表这类高频操作。这一步本质上是做“SQL方言验收”因为很多语法问题只有在真实业务SQL里才会暴露。我自己的习惯是把这层验证做成一份测试清单覆盖所有核心业务场景每项标记通过/失败留档备查。别嫌麻烦这份东西对验收、审计和后续排障都有用。4. 常见报错与排查技巧实录迁移过程中报错是常态不报错才不正常。下面这些是我在多个迁移项目里反复遇到的高频问题直接把排查思路和解决路径写出来希望帮你少走弯路。4.1 表结构阶段的高频报错“无效的列名”和“无效的表名”往往不是真的对象不存在而是大小写或标识符问题。达梦对未加引号的标识符统一按大写存储如果应用里用了混合大小写且加了引号可能就找不到了。排查时先用USER_TAB_COLUMNS查询确认实际对象名再决定是改SQL还是重建对象。“字符串长度超出”是VARCHAR口径问题的高频表现。尤其在UTF8字符集下VARCHAR(100)按字节算只能存约33个中文汉字。解决方向有两个一是确认建库参数里长度单位是字符二是把目标表的VARCHAR长度按3倍预留。具体用哪种取决于你的参数设置但别在DTS生成的DDL上盲目自信一定要抽查中文场景。还有一种是“不是GROUP BY表达式”之类的分组报错。MySQL在关闭ONLY_FULL_GROUP_BY时对分组查询很宽松达梦的默认行为可能更严格。遇到这种就调整查询把非聚合列要么加进GROUP BY要么用聚合函数包一层。4.2 数据导入阶段的高频报错乱码问题几乎每次都有人遇到。根源基本是源库导出文件字符集与目标库字符集不一致。导出时强制指定字符集比如UTF8导入时也声明同样的字符集能解决大部分乱码。如果已经乱码先确认数据是导入时就丢了还是显示层的问题再对症下药。主键或唯一键冲突常见于重复执行导入脚本。解决方法是导入前先TRUNCATE目标表或使用MERGE方式导入。如果数据量大又需要反复试错写一个TRUNCATE IMPORT的二合一脚本效率会高不少。另外要检查自增列的处理重新导入时如果不重置序列后续插入的主键可能会和存量数据冲突。“数字溢出”也经常出现原因是MySQL的UNSIGNED列映射成了达梦的带符号列。例如INT UNSIGNED最大能到42亿而达梦INT最大只有21亿。这种字段在建表时就要把映射逻辑想清楚升级到BIGINT或让应用侧接受取值范围调整。4.3 运行期SQL兼容问题最麻烦的是那种“表建好了、数据导进去了、应用一跑就报错”的情况。常见报错有函数不存在、ORA-like语法错误、字符串比较行为不同。函数不存在的排查办法很直接把报错里的函数名拿到达梦文档里查。IFNULL、DATE_FORMAT这类函数在部分兼容模式下可用但通用性不如NVL和TO_CHAR。想一劳永逸就把应用SQL里的MySQL专有函数全部统一替换成Oracle风格写法虽然前期工作量大但后续不会再被这个事反复纠缠。字符串比较行为的不同很隐蔽。MySQL的VARCHAR比较默认不区分大小写这可能让达梦的默认区分大小写行为在登录、去重等场景中与预期不符。遇到这种情况用LOWER或UPPER包一层再比较是最直接的解法。4.4 性能问题与参数调整迁移完成后性能不达标也要按几类原因去排查。索引问题最容易被忽略。数据导入时为了速度关掉的索引如果忘了重建或者DTS工具生成索引失败应用查询就会全线变慢。用系统视图查一下目标库索引数量和源库做对比这一步一分钟就能完成。统计信息过期也会导致执行计划错乱。迁移后第一件事就是刷新统计信息DBMS_STATS.GATHER_TABLE_STATS(ORDER_DB, T_USER, CASCADE TRUE);统计信息一刷新很多“说不清为什么慢”的SQL会自然恢复正常。还有内存和并发参数。达梦实例的BUFFER大小、最大会话数、并行度等参数需要根据业务量调整。数据迁移刚完成时先按源库的业务量级别设置一个合理基线再通过压力测试逐步调参不要一上来就拉满。最后提一个很容易踩的实操坑达梦的执行计划查看方式和MySQL不完全一样排查慢SQL时要习惯用达梦的EXPLAIN格式和性能视图别拿MySQL的思维方式硬套否则会绕很多弯路。写在最后这几年做过的迁移项目从几十张表到上千张表的都经历过。我最大的体会是数据库迁移这件事工具只能帮你完成前20%的工作剩下的80%靠的是对两套数据库差异的深刻理解以及一个严谨的验证流程。如果你现在正准备启动一个MySQL到达梦的迁移我建议你把重心放在“评估”和“验证”这两个环节上。评估做得越细迁移中的意外越少验证做得越严上线后的风险越小。至于迁移工具本身反而不用太纠结DTS也好、dmfldr也好上手都很快真正拉开差距的是你对数据字典、SQL改写和存储过程调试的熟练程度。最后再分享一个小技巧整个迁移过程务必保留一份完整的操作记录包括DDL脚本、导入命令、参数调整项、每个阶段的耗时和报错处理方式。这份记录在迁移验收、问题回溯、甚至后续二次迁移时价值远超你的想象。别嫌麻烦养成这个习惯比任何工具都管用。