
1. 迁移之前先把两边的“家底”摸清楚1.1 为什么Oracle到达梦的迁移总在“意料之外”翻车做过数据库迁移的人都有一个共识迁移本身不难难的是迁移之后业务跑不起来。Oracle到达梦的迁移尤其如此因为这两套数据库虽然都支持SQL标准但在数据类型、函数行为、事务机制、优化器策略上的差异远比想象中大。我参与过几个从Oracle迁到达梦的项目最小的库也有几百张表最大的接近两千张表每次迁移都会遇到一批“文档里没写、但一定会踩”的问题。这篇文章不是官方文档的复述而是把实际迁移过程中反复出现的典型问题做一个系统梳理。适合正在做或准备做Oracle到达梦迁移的开发和运维人员尤其是那些已经跑过官方迁移工具、但被后续的SQL兼容性和性能问题卡住的团队。我会从迁移前的评估、数据类型映射、SQL语法差异、存储过程改造、性能调优、数据校验几个维度展开每个问题都给出具体的现象、原因和可操作的解决方案。1.2 迁移前必须做的三项评估工作很多团队一上来就装迁移工具、建目标库、开始导数据结果做到一半发现某个核心业务模块根本跑不通返工成本极高。我的经验是动手之前至少花两三天做三件事。第一件事是对象清单盘点。把源库里的表、视图、索引、约束、触发器、存储过程、函数、序列、同义词全部拉出来按类型统计数量。重点关注存储过程和触发器这两类对象的兼容性风险最高。达梦对Oracle的PL/SQL兼容度虽然不错但仍有不少语法需要改写。第二件事是数据量分级。把表按数据量分成小表百万行以下、中表百万到千万行、大表千万行以上。不同量级的表在迁移策略上完全不同小表可以直接用工具搬大表需要考虑分批、并行和停机窗口。第三件事是业务SQL采样。从应用日志或数据库的SQL历史中抓取高频SQL尤其是那些用了Oracle特有函数的语句。这一步能提前暴露大部分兼容性问题比迁移后再逐个排查效率高得多。实操心得对象清单和数据量分级可以用一条SQL搞定查dba_objects和dba_tables关联即可。业务SQL采样建议抓取最近一周的TOP 200 SQL覆盖绝大多数核心业务场景。2. 数据类型映射最容易埋雷的环节2.1 常见数据类型对照与隐藏陷阱Oracle和达梦的数据类型看起来大同小异但细节差异足以让业务出错。下面这张表是我在实际迁移中总结的对照关系以及每个类型需要特别注意的地方。Oracle类型达梦对应类型注意事项VARCHAR2(n)VARCHAR(n)达梦默认按字节计算长度Oracle按字符含中文时需确认NUMBER(p,s)DECIMAL(p,s)精度和标度需显式指定否则可能丢精度NUMBERDECIMAL(38,?)不指定精度时达梦默认行为与Oracle不同DATEDATETIMEOracle的DATE含时分秒达梦DATE只到日必须用DATETIMETIMESTAMPTIMESTAMP基本兼容但时区处理需验证CLOBCLOB达梦CLOB操作函数与Oracle有差异BLOBBLOB大对象读写接口需适配RAW(n)VARBINARY(n)长度语义一致但函数支持不同LONGCLOB达梦不支持LONG类型必须转换最容易出问题的是VARCHAR2的长度语义。Oracle的VARCHAR2(100)在默认参数下是100个字符而达梦的VARCHAR(100)默认是100个字节。如果字段里存的是中文Oracle能存100个汉字达梦只能存33个左右。这个问题在迁移工具里通常不会报错但插入数据时会截断或报错。解决方案是在建库时设置LENGTH_IN_CHAR1参数让达梦按字符计算长度。如果库已经建好了那就只能改字段长度把VARCHAR(100)改成VARCHAR(300)来容纳同样数量的中文。2.2 DATE类型一个让业务时间错乱的坑Oracle的DATE类型包含年月日时分秒而达梦的DATE类型只包含年月日。这个差异导致很多业务在迁移后出现时间字段丢失时分秒的情况。比如订单创建时间从2024-01-15 14:30:25变成了2024-01-15 00:00:00对业务的影响是致命的。正确的做法是在迁移时把所有Oracle的DATE字段映射为达梦的DATETIME类型。达梦的DATETIME等价于Oracle的DATE包含完整的时分秒。如果迁移工具自动映射成了DATE需要手动改过来。还有一个更隐蔽的问题Oracle的SYSDATE返回当前日期时间达梦的SYSDATE也返回日期时间但达梦还有一个CURDATE只返回日期。在改写SQL时要注意区分别把SYSDATE误改成CURDATE。2.3 NUMBER类型的精度陷阱Oracle的NUMBER类型非常灵活可以不指定精度和标度也可以只指定精度不指定标度。达梦的DECIMAL类型在这一点上行为不同。如果Oracle里定义的是NUMBER不指定任何参数迁移到达梦后如果映射成DECIMAL(38,0)那么所有小数都会被截断成整数。我遇到过一个真实案例某张表的金额字段在Oracle里是NUMBER迁移到达梦后变成了整数导致所有金额都少了小数部分。排查了半天才发现是类型映射的问题。正确的做法是把Oracle的NUMBER映射为达梦的DECIMAL(38,10)或根据实际业务精度来定确保小数位不丢失。注意事项迁移前一定要跑一遍数据抽样比对重点检查金额、数量、比率这类数值字段的小数位是否完整。别等到业务上线后才发现数据精度出了问题。3. SQL语法差异改写量最大的部分3.1 函数差异与替代方案Oracle有大量特有的函数达梦虽然兼容了一部分但仍有不少需要改写。下面列出迁移中最常遇到的函数差异。NVL和NVL2达梦支持NVL但NVL2不支持。NVL2(expr, value_if_not_null, value_if_null)需要改写成CASE WHEN expr IS NOT NULL THEN value_if_not_null ELSE value_if_null END。DECODE达梦支持DECODE但嵌套DECODE在复杂场景下可能出问题。建议统一改写成CASE WHEN可读性更好兼容性也更强。ROWNUM这是Oracle分页的核心达梦不直接支持ROWNUM。达梦提供了LIMIT和OFFSET语法或者用ROW_NUMBER() OVER()窗口函数来替代。比如Oracle的SELECT * FROM t WHERE ROWNUM 10在达梦里要写成SELECT * FROM t LIMIT 10。TO_DATE和TO_CHAR达梦支持这两个函数但格式串的写法有差异。Oracle的TO_CHAR(sysdate, YYYY-MM-DD HH24:MI:SS)在达梦里要写成TO_CHAR(sysdate, YYYY-MM-DD HH24:MI:SS)看起来一样但达梦对格式串的解析更严格某些Oracle能识别的格式达梦会报错。CONNECT BYOracle的层次查询语法达梦支持但有一些限制。如果层次查询里用了复杂的函数或子查询可能需要改写成递归CTE。3.2 分页查询的改写实战分页查询是业务系统里最常见的SQL模式也是迁移中改写量最大的部分。Oracle的传统分页写法是嵌套ROWNUMSELECT * FROM ( SELECT a.*, ROWNUM rn FROM ( SELECT * FROM orders ORDER BY create_time DESC ) a WHERE ROWNUM 20 ) WHERE rn 10;在达梦里同样的逻辑可以简化为SELECT * FROM orders ORDER BY create_time DESC LIMIT 10 OFFSET 10;达梦的LIMIT/OFFSET语法更简洁但要注意OFFSET是从0开始还是从1开始。达梦的OFFSET是从0开始的所以取第11到20条记录OFFSET应该是10。如果应用层用的是MyBatis或Hibernate这类ORM框架分页通常由框架自动生成迁移时需要确认框架的达梦方言是否配置正确。有些框架的达梦方言还是基于老版本实现的生成的分页SQL可能不是最优的。3.3 隐式类型转换的坑Oracle的隐式类型转换比较宽松比如WHERE id 123id是数字类型Oracle会自动把字符串转成数字。达梦在这方面更严格某些情况下会直接报错。我遇到过一个问题某条SQL在Oracle里跑得好好的到达梦就报“数据类型不匹配”。排查发现是WHERE条件里数字字段和字符串常量比较达梦不做隐式转换。解决办法是在应用层确保参数类型正确或者在SQL里显式转换。还有一个更隐蔽的场景Oracle里空字符串等价于NULL达梦也支持这个行为但在某些函数里的处理方式不同。比如NVL(, default)在Oracle里返回default在达梦里可能返回空字符串。这个差异在数据清洗和默认值处理时特别容易出问题。4. 存储过程与触发器改造工作量大头4.1 PL/SQL兼容性分析达梦的存储过程语言叫DMSQL语法上大量借鉴了Oracle的PL/SQL但仍有不少差异。迁移中最常见的问题集中在以下几个方面。变量声明Oracle的%TYPE和%ROWTYPE在达梦里支持但用法有细微差别。比如v_name emp.ename%TYPE在达梦里要写成v_name emp.ename%TYPE看起来一样但达梦要求引用的表必须存在且在某些情况下需要加schema前缀。游标Oracle的显式游标和隐式游标在达梦里都支持但FOR UPDATE子句的行为有差异。达梦的FOR UPDATE默认锁定所有行而Oracle可以指定FOR UPDATE OF column只锁定特定列。如果存储过程里用了这个特性需要改写。异常处理Oracle的EXCEPTION WHEN NO_DATA_FOUND THEN在达梦里对应的是EXCEPTION WHEN NO_DATA_FOUND THEN但达梦的异常类型和Oracle不完全一致。比如Oracle的TOO_MANY_ROWS在达梦里可能对应不同的异常码。动态SQLOracle的EXECUTE IMMEDIATE在达梦里支持但绑定变量的写法有差异。Oracle用USING子句绑定变量达梦也支持但参数顺序和类型要求更严格。4.2 一个真实存储过程的改造案例某业务系统有一个存储过程功能是根据订单号计算折扣并更新订单表。Oracle版本的逻辑大致如下CREATE OR REPLACE PROCEDURE calc_discount(p_order_id IN NUMBER) AS v_amount NUMBER; v_level VARCHAR2(10); BEGIN SELECT amount INTO v_amount FROM orders WHERE order_id p_order_id; SELECT cust_level INTO v_level FROM customers WHERE cust_id (SELECT cust_id FROM orders WHERE order_id p_order_id); IF v_level VIP THEN UPDATE orders SET discount v_amount * 0.8 WHERE order_id p_order_id; ELSIF v_level GOLD THEN UPDATE orders SET discount v_amount * 0.9 WHERE order_id p_order_id; ELSE UPDATE orders SET discount v_amount WHERE order_id p_order_id; END IF; COMMIT; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20001, 订单不存在); END;迁移到达梦时需要做以下改写把NUMBER改成DECIMAL把VARCHAR2改成VARCHAR把RAISE_APPLICATION_ERROR改成达梦的RAISE_APPLICATION_ERROR达梦支持这个函数但错误码范围不同把COMMIT放在异常处理之外达梦对事务控制的位置更敏感。改写后的达梦版本CREATE OR REPLACE PROCEDURE calc_discount(p_order_id IN DECIMAL) AS v_amount DECIMAL(18,2); v_level VARCHAR(10); BEGIN SELECT amount INTO v_amount FROM orders WHERE order_id p_order_id; SELECT cust_level INTO v_level FROM customers WHERE cust_id (SELECT cust_id FROM orders WHERE order_id p_order_id); IF v_level VIP THEN UPDATE orders SET discount v_amount * 0.8 WHERE order_id p_order_id; ELSIF v_level GOLD THEN UPDATE orders SET discount v_amount * 0.9 WHERE order_id p_order_id; ELSE UPDATE orders SET discount v_amount WHERE order_id p_order_id; END IF; COMMIT; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20001, 订单不存在); END;看起来改动不大但实际迁移中一个几百行的存储过程可能需要改几十处。建议用自动化工具先做一轮语法转换再人工逐行审查。4.3 触发器的迁移注意事项达梦支持触发器但触发器的语法和行为与Oracle有差异。最常见的问题是触发器里的:NEW和:OLD引用。Oracle里用:NEW.column_name和:OLD.column_name达梦里也支持但在某些版本里需要写成NEW.column_name去掉冒号。另一个问题是触发器的执行时机。Oracle的BEFORE INSERT触发器可以在插入前修改:NEW的值达梦也支持但修改方式有差异。Oracle里直接赋值:NEW.column : value达梦里可能需要用SET NEW.column value。还有一个坑是触发器的递归调用。Oracle有PRAGMA AUTONOMOUS_TRANSACTION可以控制触发器里的事务达梦的对应机制不同。如果触发器里做了DML操作要特别注意事务的提交和回滚行为。实操心得触发器迁移后一定要做完整的业务回归测试尤其是涉及数据变更的触发器。我见过一个案例触发器在Oracle里正常工作迁移到达梦后因为事务隔离级别不同导致数据不一致。5. 性能调优迁移后的必修课5.1 执行计划差异与索引优化Oracle和达梦的优化器策略不同同样的SQL和索引在两边的执行计划可能完全不一样。迁移后最常见的性能问题是全表扫描代替了索引扫描。原因通常有几个一是达梦的统计信息没有及时收集优化器无法做出正确判断二是索引在迁移过程中丢失或失效三是SQL改写后导致索引无法命中。解决办法是迁移完成后立即做三件事第一对所有表执行ANALYZE TABLE收集统计信息第二检查所有索引是否创建成功特别是函数索引和复合索引第三对核心SQL跑一遍执行计划对比迁移前后的差异。达梦查看执行计划的命令是EXPLAIN SELECT ...或者用EXPLAIN FOR。建议把核心SQL的执行计划都过一遍重点关注是否有全表扫描、是否有排序操作、是否有嵌套循环层数过多的问题。5.2 绑定变量与游标共享Oracle对绑定变量的游标共享支持很好达梦也支持但行为有差异。在Oracle里SELECT * FROM t WHERE id :1和SELECT * FROM t WHERE id :2会共享同一个游标。达梦的游标共享机制类似但对参数类型的敏感度更高。如果应用层用的是JDBC的PreparedStatement通常没问题。但如果用的是Statement拼接SQL迁移后可能会出现大量硬解析导致CPU飙升。建议在迁移前就把应用层的SQL改成绑定变量方式这不仅能提升达梦的性能也是数据库开发的最佳实践。5.3 并行查询与资源控制达梦支持并行查询但默认可能是关闭的。对于大表扫描和复杂聚合开启并行能显著提升性能。达梦的并行度可以通过PARALLEL提示或系统参数来控制。不过并行不是万能的。如果系统CPU核数有限或者并发业务量很大开启并行反而会加剧资源竞争。我的经验是对于OLAP类的报表查询可以适当开启并行对于OLTP类的高并发短查询保持串行更好。达梦还提供了资源限制功能可以限制单个会话的CPU、内存和IO使用量。在混合负载场景下这个功能很有用能防止某个大查询拖垮整个系统。6. 数据校验与一致性保障6.1 迁移后的数据比对方法数据迁移完成后必须做数据校验确认源库和目标库的数据完全一致。校验方法有三种各有优劣。全量比对对每张表的每行数据做逐字段比对。最准确但耗时最长。适合小表和对一致性要求极高的核心表。抽样比对随机抽取一定比例的数据做比对。速度快但可能漏掉问题。适合大表的初步校验。聚合比对对每张表做COUNT、SUM、MIN、MAX等聚合计算对比两边的结果。速度快能发现大部分数据问题但无法发现行内的字段级差异。我的建议是三者结合核心表全量比对大表聚合比对加抽样比对非核心表聚合比对即可。比对工具可以用达梦自带的dmfldr和dmdba也可以自己写脚本。6.2 常见数据不一致的原因迁移后数据不一致的原因通常有这几类字符集问题导致中文乱码、数据类型转换导致精度丢失、空值处理差异导致NULL变成空字符串、约束冲突导致部分数据被过滤。字符集问题最麻烦因为一旦数据写入时编码错了后续修复很困难。迁移前一定要确认源库和目标库的字符集一致至少要是兼容的字符集。Oracle常用ZHS16GBK或AL32UTF8达梦对应GBK或UTF-8。如果源库是ZHS16GBK目标库是UTF-8迁移时要做字符集转换否则中文会乱码。空值处理差异也很常见。Oracle里和NULL在大多数场景下等价达梦也类似但在某些函数和比较操作里行为不同。比如WHERE column 在Oracle里永远不成立因为NULL不等于任何值在达梦里可能成立。这个差异会导致查询结果不一致。6.3 校验脚本示例下面是一个简单的数据校验脚本用Python实现对比源库和目标库的行数和关键字段的聚合值。import cx_Oracle import dmPython # 源库连接 oracle_conn cx_Oracle.connect(user/passhost:port/service) oracle_cursor oracle_conn.cursor() # 目标库连接 dm_conn dmPython.connect(useruser, passwordpass, serverhost, port5236) dm_cursor dm_conn.cursor() tables [orders, customers, products] for table in tables: # 行数比对 oracle_cursor.execute(fSELECT COUNT(*) FROM {table}) oracle_count oracle_cursor.fetchone()[0] dm_cursor.execute(fSELECT COUNT(*) FROM {table}) dm_count dm_cursor.fetchone()[0] if oracle_count ! dm_count: print(f表 {table} 行数不一致: Oracle{oracle_count}, 达梦{dm_count}) else: print(f表 {table} 行数一致: {oracle_count}) # 关键字段聚合比对 oracle_cursor.execute(fSELECT SUM(amount), MIN(create_time), MAX(create_time) FROM {table}) oracle_agg oracle_cursor.fetchone() dm_cursor.execute(fSELECT SUM(amount), MIN(create_time), MAX(create_time) FROM {table}) dm_agg dm_cursor.fetchone() if oracle_agg ! dm_agg: print(f表 {table} 聚合值不一致: Oracle{oracle_agg}, 达梦{dm_agg}) else: print(f表 {table} 聚合值一致) oracle_cursor.close() oracle_conn.close() dm_cursor.close() dm_conn.close()这个脚本可以根据实际需求扩展比如增加字段级的比对、增加更多表的校验、增加异常处理等。7. 迁移工具选型与使用技巧7.1 达梦官方迁移工具DTS的使用要点达梦提供了DTSData Transfer Service工具支持从Oracle到达梦的迁移。DTS的优点是图形化界面、操作简单、支持大部分对象的自动转换。但实际使用中也有不少需要注意的地方。第一DTS的自动转换不是100%准确。存储过程、触发器和复杂视图的转换经常需要人工干预。建议先用DTS做一轮自动转换然后人工审查所有转换后的对象。第二DTS的迁移速度受网络和配置影响很大。如果源库和目标库不在同一台机器上建议先用DTS做结构迁移再用dmfldr做数据迁移。dmfldr是达梦的快速加载工具比DTS的逐行插入快得多。第三DTS的日志要仔细看。迁移过程中DTS会输出大量日志其中包含转换失败的对象和警告信息。这些信息是后续人工修复的重要依据千万别忽略。7.2 第三方工具与自研脚本的取舍除了DTS市面上还有一些第三方迁移工具比如某数据迁移平台、某云厂商的迁移服务等。这些工具各有特点但核心能力大同小异。选择时重点看三点对Oracle特有语法的兼容度、对达梦目标端的支持程度、是否支持增量迁移。如果团队有开发能力自研迁移脚本也是一个选择。自研的好处是可控性强能针对特定业务场景做优化。坏处是工作量大需要处理各种边界情况。我的建议是如果迁移规模不大几百张表以内用DTS加人工修复就够了如果规模很大上千张表可以考虑自研脚本加DTS混合使用。7.3 迁移过程中的停机窗口管理迁移最大的挑战之一是停机窗口。业务系统不能停太久但数据迁移又需要时间。我的经验是分阶段迁移先迁移静态数据比如配置表、字典表这些表可以在业务运行期间迁移再迁移动态数据比如订单表、日志表这些表需要在停机窗口内迁移。停机窗口内要做的事情包括停止应用写入、迁移增量数据、校验数据一致性、切换连接串、启动应用。每一步都要提前演练确保在窗口内能完成。如果数据量特别大可以考虑用达梦的CDC变更数据捕获功能做增量同步把停机窗口压缩到最小。实操心得停机窗口的预估时间要留足余量实际耗时通常是预估的1.5到2倍。我见过一个项目预估停机4小时结果因为一个存储过程改造卡住实际停了8小时。提前做好回滚方案万一迁移失败能快速切回Oracle。8. 常见问题速查与避坑清单8.1 迁移问题速查表问题现象可能原因解决方案中文乱码字符集不一致确认源库和目标库字符集必要时做转换时间字段丢失时分秒DATE类型映射错误把Oracle的DATE映射为达梦的DATETIME金额小数位丢失NUMBER类型精度不足把NUMBER映射为DECIMAL(38,10)或按业务精度指定分页查询报错ROWNUM不支持改用LIMIT/OFFSET或ROW_NUMBER()存储过程编译失败PL/SQL语法差异逐行审查改写不兼容的语法查询性能下降统计信息未收集执行ANALYZE TABLE收集统计信息索引未命中索引丢失或SQL改写导致检查索引创建情况对比执行计划数据不一致空值处理或类型转换差异做全量或抽样数据比对触发器不生效触发器语法或事务差异检查触发器创建语句测试事务行为连接池报错JDBC驱动或连接串配置错误确认达梦JDBC驱动版本和连接串格式8.2 独家避坑技巧技巧一迁移前先在测试环境跑一遍完整流程。包括结构迁移、数据迁移、应用连接、业务回归。测试环境的问题越多生产环境的问题越少。技巧二保留Oracle库至少一个月。迁移后不要急着下线Oracle万一达梦这边出问题还能快速切回去。等业务稳定运行一个月后再考虑下线。技巧三监控达梦的性能指标。迁移后要重点关注CPU、内存、IO、锁等待、慢查询这几个指标。达梦提供了系统视图和性能监控工具可以实时查看。技巧四应用层的SQL要逐条审查。不要假设ORM框架生成的SQL一定兼容有些框架的达梦方言可能不完善。建议把核心业务的SQL都抓出来在达梦里跑一遍。技巧五建立迁移知识库。把迁移过程中遇到的问题、解决方案、改写后的SQL都记录下来形成团队的知识库。下次再做迁移时能节省大量时间。8.3 迁移后的持续优化方向迁移完成不是终点而是起点。达梦和Oracle在很多方面还有差异需要持续优化。比如达梦的索引组织表、分区表、物化视图等功能与Oracle有差异需要根据业务特点重新设计。达梦的备份恢复策略、高可用方案、监控体系也需要重新建立。我的建议是迁移后设立一个月的观察期期间重点做三件事性能调优、稳定性验证、运维体系搭建。性能调优包括SQL优化、索引优化、参数调优稳定性验证包括压力测试、故障演练、数据一致性检查运维体系搭建包括备份策略、监控告警、应急响应流程。这个内容后续还可以这样扩展针对特定业务场景比如电商、金融、政务的迁移方案细化针对达梦新版本特性的适配针对国产化替代整体方案的设计。每个方向都值得深入展开但核心思路是一致的理解差异、提前评估、充分测试、持续优化。