ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MySQL迁移达梦数据库全流程实战:结构、数据、SQL方言到应用适配

MySQL迁移达梦数据库全流程实战:结构、数据、SQL方言到应用适配 这两年国产化数据库替换的项目越来越多MySQL到DM达梦的迁移几乎成了标配任务。老实说我第一次接到迁移任务时也以为就是“导出表结构、灌入数据”两小时收工的事真上手才发现从字段类型、自增列、存储过程到JDBC连接串处处有坑。这篇文章是我做了多个迁移项目后沉淀下来的一套完整流程覆盖迁移前的准备、工具选型、结构迁移、数据搬家、SQL方言改写、失败排查到最后的验证与适配尽量把能踩的坑提前标出来。如果你正在处理“MySQL迁达梦”的活儿这篇应该能让你少走几段弯路。1. 迁移调研与准备先别急着装工具很多人拿到迁移任务第一件事就是打开DTS工具拖拽表然后失败、报错、网上搜解决方案折腾两天发现是流程顺序反了。迁移之前真正该做的是先盘清楚自己的MySQL到底长什么样再决定达梦这边怎么初始化。1.1 盘点源端MySQL的“家底”这一步不涉及任何数据库迁移技术但对后续影响巨大。我会拿个表格逐项登记确保心里有数检查项具体要确认的内容为什么重要MySQL版本5.7还是8.0官方版还是云厂商RDS5.7和8.0在默认字符集、排序规则、SQL行为上有差异数据规模总数据量、表数量、单表最大行数、大字段总量决定全量迁移还是分批次迁移以及是否要用高速导入工具对象清单存储过程、函数、触发器、事件、视图、自定义函数这些是迁移中报错最集中的区域需要额外花时间改写应用侧依赖使用JDBC/ODBC/ORM是否用了MySQL特有函数SQL里有没有方言迁移不只是搬数据应用连接串、SQL语法都要跟着调整建议直接用SQL统计行数和容量类似这样SELECT table_schema, table_name, table_rows, ROUND((data_length index_length) / 1024 / 1024, 2) AS size_mb FROM information_schema.tables WHERE table_schema 你的库名 ORDER BY size_mb DESC;注意table_rows是估算值但排序足够用了。另一个容易漏掉的是MySQL库里的sql_mode比如启用了ONLY_FULL_GROUP_BY很多看似正常的SQL在严格模式下会失败。迁移到达梦后达梦对分组查询的校验也有自己的逻辑这类SQL往往是最先暴露问题的。1.2 达梦实例的“兼容性”该怎么规划达梦有一个COMPATIBLE_MODE参数可以在实例初始化时设为MySQL兼容模式。很多人迁移前不考虑这个创建库直接欧拉一套默认配置结果建表语句各种报错。如果条件允许建议在初始化实例时就设置兼容模式dminit PATH/data/dmdata DB_NAMEDMDB INSTANCE_NAMEDMSERVER PORT_NUM5236 COMPATIBLE_MODE4COMPATIBLE_MODE不同版本取值含义不完全一样一般0是Oracle兼容1是MySQL兼容4可能对应MySQL 8.0你在做之前最好用dminit help确认一下手册。已经建好实例的话也可以查看当前值SELECT * FROM V$PARAMETER WHERE NAME COMPATIBLE_MODE;但要注意兼容模式不是万能开关它主要影响SQL语法解析和部分系统函数行为解决不了所有迁移问题后续的数据库对象还是需要人工校对。另外确认达梦版本是否为最新的稳定版很多奇怪问题在补丁版本中已经修复能推到新版本就不要在旧版本上死磕。2. 迁移工具的取舍DTS之外还有别的选择谈到数据迁移大家最熟悉的是达梦自带的DTS图形化工具。它确实好用但我也见过不少项目把它当成唯一方案结果被坑得很惨。正确的思路是了解每种工具的边界组合使用。2.1 达梦自带DTS工具的真实体验DTSData Transfer Service通常装在达梦数据库客户端目录下打开后可以配置MySQL数据源把表结构、数据直接迁移过来。优点是操作直观能自动把MySQL字段类型映射成达梦类型也能生成迁移报告。缺点也很明显大表迁移时容易内存占用过高甚至连接中断。存储过程、函数、触发器的迁移成功率不高很多时候要手动改写。默认映射保守生成的字段类型不一定最优。大批量对象同时迁移时出错后定位困难。我的经验是小表、简单表直接用DTS没问题但千万不要一次性勾选几十张表然后点“开始”。更稳的做法是先同步表结构再同步数据分两步执行。DTS的日志里会详细记录每张表的行数和耗时一旦中间失败优先看“日志信息”页签通常能看到具体是哪个表因为什么原因失败。如果日志里提示“无法加载mysql驱动”多半是DTS没找到MySQL的JDBC驱动包去达梦安装目录的drivers/jdbc下放一份并设置好驱动类名com.mysql.jdbc.Driver或com.mysql.cj.jdbc.Driver。2.2 为什么推荐“半自动”的组合策略我会把工具分成几类结构迁移工具、数据同步工具、命令行工具。中小型表用DTS全自动大表和复杂对象用“手工SQL 命令行工具”半自动处理。原因很简单工具适合批量、重复、简单流程但碰到真正棘手的大对象时工具的处理策略往往不是最优解。比如一张几亿行的日志表DTS一条一条INSERT进去会非常慢这时候用达梦的dmfldr高速加载工具或者dmexp/dmpimp反而更有优势。所以我的迁移流程通常这么安排先用DTS同步所有小表和中等表完成后立即校验。大表单独导出成文本文件再通过dmfldr批量导入。存储过程、函数、触发器、视图等手工迁移不指望工具自动转。最后通过SQL脚本补索引、约束、注释和触发器等附加对象。这样做的逻辑很清楚让工具处理它擅长的事复杂场景人为介入避免“一刀切”带来的返工。3. 结构迁移的逐点对照数据类型、约束与默认值这部分是整个迁移中最容易被低估的环节。表面看都是CREATE TABLE但MySQL和达梦的字段类型体系差异很大直接照搬要么报错要么生成性能很差的表结构。3.1 MySQL到DM的数据类型映射表下面是一份我整理过的常用映射参考按经验不断修正过可以当个速查表MySQL类型达梦类型说明TINYINTTINYINT / SMALLINT如果TINYINT(1)表示布尔值建议迁移为SMALLINT或BIT避免应用层取值类型混淆SMALLINTSMALLINT无符号类型建议升一级到INT因为达梦对无符号支持比较弱MEDIUMINT / INT UNSIGNEDINT / BIGINT无符号INT必须升为BIGINT否则溢出BIGINTBIGINT注意达梦的BIGINT范围与MySQL一致DECIMAL / NUMERICDECIMAL / NUMERIC精度、标度保持一致即可FLOAT / DOUBLEFLOAT / DOUBLE注意浮点比较的误差行为必要时改为DECIMALCHAR / VARCHARCHAR / VARCHAR注意字符长度单位达梦默认按字符计算和MySQL的字符语义一致但多字节字符集下要确认TEXT / TINYTEXT / MEDIUMTEXT / LONGTEXTTEXT / CLOB达梦TEXT等价于CLOB长文本类型建议用CLOBBLOB / BINARY / VARBINARYBLOB / VARBINARY二进制类型也要按长度和使用方式调整DATETIMETIMESTAMP / DATETIME达梦有DATETIME也可以映射TIMESTAMPTIMESTAMPTIMESTAMP注意MySQLON UPDATE CURRENT_TIMESTAMP行为达梦不支持DATEDATE好迁移JSONCLOB / TEXT达梦有JSON类型不同版本支持度不同建议保守地映射为TEXT或CLOB应用层再自己解析ENUM / SETVARCHAR / 自定义约束建议转换为VARCHAR并在应用层维护枚举值否则通过CHECK约束管理有限值GEOMETRYST_GEOMETRY复杂空间数据类型除非业务明确用到否则迁移成本高映射之外还要注意字符集问题。MySQL常用utf8mb4达梦建库时建议用UTF-8字符集不一致会导致中文乱码、字符串比较异常甚至在导入时报“字符串转换错误”。检查达梦库字符集可以看初始化参数CHARSET用SELECT * FROM V$DATABASE;查看相关信息。3.2 自增列、主键与默认值的处理MySQL的自增列在达梦里有两种方案IDENTITY列或者序列加触发器。IDENTITY列最简单建表时可以直接写CREATE TABLE user_info ( id INT IDENTITY(1,1) PRIMARY KEY, name VARCHAR(64), create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );但要注意达梦的IDENTITY列不是所有场景都能直接兼容。如果原表已经把AUTO_INCREMENT列作为复合主键的一部分迁移时建议先建普通列再通过序列加触发器实现自增。序列方案长这样CREATE SEQUENCE seq_user_id START WITH 1 INCREMENT BY 1; CREATE OR REPLACE TRIGGER trg_user_info_id BEFORE INSERT ON user_info FOR EACH ROW BEGIN IF :NEW.id IS NULL THEN SELECT seq_user_id.NEXTVAL INTO :NEW.id FROM DUAL; END IF; END;这个方案能保住现有主键值也方便后续偶尔手工指定ID。再来看默认值MySQL最常见的两个默认值函数CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP。达梦支持DEFAULT CURRENT_TIMESTAMP但“行更新时自动更新时间戳”没有等价语法需要另建触发器实现CREATE OR REPLACE TRIGGER trg_user_info_upd BEFORE UPDATE ON user_info FOR EACH ROW BEGIN :NEW.update_time CURRENT_TIMESTAMP; END;如果原表里有大量这种字段迁移前先统计一下分批写成触发器模板能省不少事。4. 数据搬家的完整流水线导出、传输、导入结构弄完后就是数据搬迁。这个阶段最怕的是“一把梭”几千万行的表直接跑INSERT不仅慢还会因为事务日志膨胀导致数据库异常。我按表规模把数据迁移分成三条路。4.1 大表迁移的切分方案单表超过500万行或者整体超过2GB的我建议不要依赖DTS的INSERT方式而是走“文本导出 高速加载”。具体步骤是先在MySQL端把数据导成文本文件再传到达梦服务器上用dmfldr批量装载。MySQL导出文本用mysqldump的--tab参数是最省事的mysqldump -uroot -p --no-create-info --tab/data/export/ --fields-terminated-by, --lines-terminated-by\n your_db your_big_table注意--tab会为每个表生成一个.txt文件和一个.sql文件.sql里是INSERT语句.txt是纯数据。我们只要.txt因为接下来要喂给达梦的dmfldr。到了达梦这边需要手动写一个控制文件big_table.ctl内容类似LOAD DATA INFILE /data/export/your_big_table.txt INTO TABLE your_big_table FIELDS TERMINATED BY , TRAILING NULLCOLS ( id, name, create_time )然后执行dmfldr USERIDSYSDBA/your_passwordlocalhost:5236 CONTROL/data/export/big_table.ctl这个工具很能吃数据几亿行的表也能较快跑完。但有一点要提醒如果原MySQL的字段是NULL而文本里用了空字符串表示dmfldr的TRAILING NULLCOLS会让它们变成NULL要根据业务语义仔细核对。更安全的做法是在MySQL导出时用固定的空值标识比如把NULL统一转成\N再在控制文件里用NULLIF (column_nameBLANKS)之类的语法处理。4.2 导入时的批次提交与错误处理不管用什么方式导入批次提交都很重要。dmfldr默认会按行数分批提交可以设置ROWS10000指定每个批次的行数这样一个批次失败不会影响已经提交的部分排查问题也方便。命令里加一行dmfldr USERIDSYSDBA/your_passwordlocalhost:5236 CONTROL/data/export/big_table.ctl ROWS10000如果担心坏数据打断整个任务可以加上ERRORS1000表示最多容忍1000行错误超过再终止同时用LOG/data/export/big_table.log指定日志路径。迁移完成后第一件事就是比对行数SELECT COUNT(*) FROM your_big_table;和源库的SELECT COUNT(*)对照。如果对不上优先查dmfldr日志里的错误行大多是字符集、数值溢出或字段含义不一致的问题。字符集不一致的典型场景MySQL导出时是UTF-8达梦库是GBK干垃圾数据乱码但能进去行数对得上可内容不对。所以dmfldr控制文件里也能指定字符集不要偷懒省略。5. 存储过程、函数和触发器的SQL方言改写这部分才是迁移工作真正的深水区。表结构可以靠工具自动映射但存储过程和函数里的SQL方言必须人工介入。MySQL的SQL风格和达梦的PL/SQL风格差异非常大如果团队里有人之前写过Oracle会比较有优势。5.1 MySQL与达梦在PL/SQL上的主要差异先看一个最典型的差异存储过程的整体结构。MySQL写法DELIMITER $$ CREATE PROCEDURE sp_get_user(IN p_id INT) BEGIN DECLARE v_name VARCHAR(64); SELECT name INTO v_name FROM user_info WHERE id p_id; SELECT v_name; END$$ DELIMITER ;达梦写法CREATE OR REPLACE PROCEDURE sp_get_user(p_id INT) AS v_name VARCHAR(64); BEGIN SELECT name INTO v_name FROM user_info WHERE id p_id; PRINT v_name; END;几个关键差异点MySQL的DELIMITER是客户端命令达梦不需要。达梦的变量声明在AS和BEGIN之间MySQL的DECLARE在BEGIN内。MySQL的SELECT xx;直接返回结果集达梦要用PRINT或通过OUT参数返回或者用RETURN。达梦支持CREATE OR REPLACEMySQL 8.0之前的版本不支持直接OR REPLACE。还有异常处理MySQL用DECLARE ... HANDLER FOR ...达梦用EXCEPTION WHEN ... THEN两者完全是两套逻辑。比如一个“捕获任意异常并输出错误码”的存储过程MySQL写法有SQLEXCEPTION达梦则是EXCEPTION WHEN OTHERS THEN PRINT SQLERRM;所以迁移存储过程不是把语法改一改就完事而是要把整体逻辑重写一遍。5.2 常用改写套路游标、函数、排序游标方面MySQL里常用DECLARE cur CURSOR FOR ...达梦同样支持游标而且更贴近Oracle的FOR ... LOOP写法。比如FOR rec IN (SELECT id, name FROM user_info LIMIT 10) LOOP PRINT rec.id || : || rec.name; END LOOP;这里面的LIMIT 10要改成达梦的FETCH FIRST 10 ROWS ONLY或者WHERE ROWNUM 10。PRINT rec.id || : || rec.name用到了||字符串连接符达梦支持这种写法。函数替换方面列出几个高频等价关系遇到直接对照改MySQL达梦说明IFNULL(a,b)NVL(a,b) 或 IFNULL(a,b)达梦兼容IFNULL但NVL更通用CONCAT(a,b)a || b 或 CONCAT(a,b)推荐用||中文环境少踩隐式转换GROUP_CONCAT(x)LISTAGG(x, ,)达梦有LISTAGG聚合函数NOW() / SYSDATE()SYSDATE / NOW()达梦可用SYSDATEDATE_ADD / DATE_SUBDATEADD(d, n, date)参数顺序不同FIND_IN_SET(str, list)无内置等价需要改写为INSTR(,LIMIT offset,countFETCH FIRST count ROWS ONLY / OFFSET ... ROWS FETCH兼容性视版本而定还有一点经常被忽略MySQL的字符串和数字比较会自动隐式转换比如WHERE col 123如果col是VARCHARMySQL会把col转成数字比较。达梦在这类场景下有时会报“无效的数值”有时会走索引失效。迁移SQL时最好把条件写得更明确避免依赖数据库自动转换。5.3 如何批量改写存储过程才不崩溃迁移几十个存储过程时不建议一个一个人肉改也不建议全部用工具自动转。我的办法是分成两步先用正则做一批机械替换。比如把IFNULL(换成NVL(把LIMIT n统一标出来把DECLARE cur CURSOR FOR整理成规范格式。这一步能把50%的重复劳动解决掉。然后打开达梦的管理工具逐个创建每个存储过程靠编译报错来逼出剩余问题。达梦有DBMS_UTILITY.FORMAT_ERROR_BACKTRACE编译失败后会显示第几行错误大多数情况下看到“语法错误”就知道是哪类问题。更保险的方式是建立一个空表来记录每个对象的迁移状态对象名类型原MySQL行数改写耗时编译状态备注sp_get_userPROCEDURE3520分钟通过改成PL/SQL结构trg_user_info_updTRIGGER1215分钟通过时间戳逻辑重写每次迁移完一个对象就打勾最后统一做测试。这样心里有底不会出现“迁移完了但不知道哪个过程编译失败”的状态。6. 迁移失败的排查链路从日志到数据核对迁移过程中八成会碰到各种报错遇到问题别慌先看日志再定位最后核数据。这套链路能解决大多数迁移故障。6.1 最容易踩的坑和日志定位法常见迁移报错大概有这几类连接MySQL失败驱动不存在、驱动类名不对、MySQL端不允许远程连接、SSL握手失败。建表失败字段类型不存在、默认值函数不兼容、字符集不支持。数据导入失败数值溢出、字符串太长、日期格式不对、NULL与空串混淆。存储过程编译失败语法差异、变量未声明、系统函数不存在。排查时优先看达梦服务端日志位置一般在达梦数据目录的/log下文件名类似dmserver_xxxx.log。实时跟踪的方法tail -f $DM_HOME/log/dmserver_*.log如果是通过DTS工具迁移工具自己的日志里会有更清晰的错误描述。我遇到过一个典型问题源库字段是DATETIME值是0000-00-00 00:00:00达梦默认不允许这种非法日期导入直接报错。解决方法有两个要么在MySQL端把这类值改成NULL要么在达梦端的表的日期类型上加上ALLOW_INVALID_DATES兼容项。后者不是万能的很多版本还是会拒绝所以最好在数据清洗阶段处理掉。6.2 数据一致性校验不能只数行数行数一样不代表数据一样。我见过最坑的情况是CHAR字段尾部空格被自动去掉导致内容比对不一致还有浮点字段四舍五入后产生微小差异。所以迁移完成后做三轮校验。第一轮校验行数-- 源库 SELECT COUNT(*) FROM db1.t1; -- 达梦 SELECT COUNT(*) FROM t1;第二轮校验关键字段的摘要可以先生成MD5校验值。比如对一个大表取某些数值列的和、最大值、最小值快速找出偏移-- 源库 SELECT COUNT(*), SUM(amount), MIN(create_time), MAX(create_time) FROM payment_record; -- 达梦 SELECT COUNT(*), SUM(amount), MIN(create_time), MAX(create_time) FROM payment_record;如果完全一致基本可以放心。如果希望更精细可以对每个字段做一次CHECKSUM聚合比如用BIT_XOR或MD5拼接字符串。网上有现成的MySQL全库校验脚本可以改造成达梦版但要注意达梦的字符串拼接函数和聚合函数差异。第三轮做抽样明细比对选几条关键业务记录整行导出后对比。这一步最好通过应用侧的测试人员一起参与因为他们更清楚业务上哪些字段不允许误差。7. 迁移后的应用适配与性能调优数据库搬完不等于项目完工应用连接和SQL性能往往才是真正的“最后一公里”。连接不上达梦、连接池狂报错、原来秒出的SQL现在卡死这些我都遇到过。7.1 JDBC驱动和连接池参数调整达梦提供了JDBC驱动DmJdbcDriver18.jar主流版本对应Java 8。应用侧要把原来的MySQL驱动换成达梦驱动并修改驱动类名和连接串// MySQL Class.forName(com.mysql.cj.jdbc.Driver); String url jdbc:mysql://127.0.0.1:3306/dbname; // 达梦 Class.forName(dm.jdbc.driver.DmDriver); String url jdbc:dm://127.0.0.1:5236/dbname;如果应用用的是连接池比如HikariCP、Druid注意达梦的默认端口是5236连接串格式是jdbc:dm://ip:port/schema。Druid里还要配置connection-properties很多时候需要加上compatibleModemysql以及caseSensitive等参数。这里建议到达梦官方文档里查对应你版本的最佳参数组合不要照搬社区里老旧的配置。如果应用之前用了MyBatis/JPAXML里某些MySQL专用SQL要逐条检查。特别是分页查询MySQL用LIMIT达梦支持LIMIT还是需要改成FETCH FIRST ? ROWS ONLY取决于版本和兼容模式稳妥做法是改用物理分页插件或统一写成WHERE ROWNUM ...的旧式写法。7.2 常见慢SQL的改写思路结构迁移后原MySQL的索引可能在达梦上变得无效或者失效了。很可能出现同一个SQL在MySQL走索引很快到达梦却全表扫。原因通常是字段类型变了隐式转换导致索引失效。字符集排序规则不同导致无法利用索引范围扫描。达梦优化器对某些运算符支持不如MySQL比如LIKE %xx%。函数依赖索引如DATE(create_time) ?如果没建函数索引就慢。遇到慢SQL先通过EXPLAIN看执行计划EXPLAIN SELECT * FROM payment_record WHERE user_id 123;对比达梦执行计划里是CSCN全表扫描还是CSEK索引查找然后判断是否要给SQL加上正确的索引。达梦的索引命名最好不要和MySQL的索引名冲突迁移时要注意索引名唯一性。我在项目里遇到过两个表有同名索引建第二个索引时报“对象已存在”就是因为MySQL不同表索引名可以重复而达梦的普通索引对象在Schema内一般是唯一的。解决方法是改索引名或者借助工具在迁移时统一加上表名前缀。性能验证方面建议把线上核心SQL在达梦环境跑一遍压测对比前后95分位耗时和吞吐量。如果差距明显优先看不变量执行计划、索引状态、统计信息。达梦的统计信息也像Oracle一样需要定期收集刚导入完数据后统计信息可能不准确记得执行DBMS_STATS.GATHER_TABLE_STATS或达梦的管理工具里“更新统计信息”功能否则优化器会产生非常离谱的执行计划。另外应用侧的超时时间也要调整。原MySQL连接串可能有connectTimeout3000等参数连达梦时如果网络质量一般或者首次建连要做SSL握手很容易超时。我习惯把connectTimeout放到5秒以上socketTimeout根据业务调用时长调整避免迁移后应用“假死”。迁移这个活儿从来不是把数据倒过去就完事而是一个从结构到数据再到应用的系统工程。我个人的体会是先把流程拆碎每走一步都留痕、核对比憋大招一次成功靠谱得多。最后再分享一个小习惯每天迁移工作结束时把当天的报错截图、日志片段和解决思路整理成简短的笔记遇到重复问题直接翻笔记效率会翻倍。你接手下一个迁移项目时这笔“经验账”会比自己反复踩坑值钱得多。
返回列表