数据库迁移这件事,网上教程十有八九是 GUI 截图流:打开 DTS、填源填目标、点下一步、等进度条。看着挺顺,但生产服务器多半没图形界面,我这台连 X11 都没有——DISPLAY 是空的,GUI 版 DTS 双击都起不来。这篇写的就是这种环境下的真实路径:用 DM9 自带的 DTS 命令行(dts_cmd_run.sh),把一台 Oracle 11.2 上 WUSHAN 模式的 4 张表、110 万行数据,连结构带索引搬到 DM9,全程 20.59 秒,17 个任务 0 失败,行数一条不差。
中间踩了三个坑,每个都不是看报错就能秒懂的:一个报"网络通信异常"实际是驱动文件顶错了,一个命令敲下去"没反应"只剩一屏帮助,一个在分析阶段直接 NPE。这篇不写"我踩了坑、我解决了"这种结论式复盘,把每个坑的排查过程、当时看到的原始报错、以及最后怎么定位到根因的证据都摆出来——包括我把 DTS 的 jar 包反编译开、在源码里找到"默认驱动类名挂在 6 代驱动上"那几行代码的过程。照着一遍能走完,真卡住了也知道往哪看。
环境一句话说清
| 角色 | 配置 |
|---|---|
| 源库 | Oracle 11.2,10.168.1.250:1521/orcl,system 用户,WUSHAN 模式 |
| 目标库 | DM9 企业版 9.1.0.26,本机 127.0.0.1:5236,SYSDBA |
| 操作系统 | 麒麟 V10 SP1,8 核 15G,无图形界面 |
| 迁移对象 | WUSHAN 的 4 张表:CLASS(60)、PERF_TEST(1010)、STUDENT(110 万)、STUDENTSCORES(1000) |
选 STUDENT 表当主角是因为它有 110 万行,够看 DTS 在数据搬运上的真实吞吐,而不是拿几千行的小表糊弄人。
先说"无图形界面"到什么程度。这台服务器不是"没装桌面"那么简单,是连 X 转发都不给你开的环境:
$ echo "DISPLAY=[$DISPLAY]" DISPLAY=[] # 环境变量是空的,没有 X display $ rpm -qa | grep -cE '^xorg|xfce|gnome-shell' 27 # X 相关库装了 27 个,但没有桌面会话
不死心,试过直接启动 GUI 版 DTS,结果它连窗口都开不出来,只往日志里扔了一句:
$ cd $DM_HOME/tool && ./dts An error has occurred. See the log file /dm9/dmdbms/tool/workspace/data/dts/.metadata/.log.
X 库有、DISPLAY 没有,Java 的 GUI 起不来。这条路堵死之后,剩下的选项就是 dts_cmd_run.sh 命令行——本文后面全部操作都发生在 SSH 会话里。
先说结果:20.59 秒,17 个任务全绿
直接贴最后一次干净迁移的完整日志(已把每步耗时标注清楚):
[SOURCE] jdbc:oracle:thin:@//10.168.1.250:1521/orcl([JDBC]:21.9 [DB]:11.2) [DEST] jdbc:dm://127.0.0.1:5236([JDBC]:9.1 [DB]:9.1) [START]{11}START TRANSFORM... [TASK]@ANALYZE_TABLE:END:TIME=5 second(s) 190 millisecond(s) // 4 张表结构分析 [TASK]{table-"WUSHAN.CLASS":"SYSDBA.CLASS"}@CREATE_TABLE:END:TIME=20 millisecond(s) [TASK]{table-"WUSHAN.CLASS":"SYSDBA.CLASS"}@COPY_DATA:END:COPIED=60:TIME=25 millisecond(s) [TASK]{table-"WUSHAN.PERF_TEST":"SYSDBA.PERF_TEST"}@COPY_DATA:END:COPIED=1010:TIME=148 millisecond(s) [TASK]{table-"WUSHAN.STUDENTSCORES":"SYSDBA.STUDENTSCORES"}@COPY_DATA:END:COPIED=1000:TIME=29 millisecond(s) [TASK]{table-"WUSHAN.STUDENT":"SYSDBA.STUDENT"}@COPY_DATA:END:COPIED=1101099:TIME=14 second(s) 1 millisecond(s) [TASK]{table-"WUSHAN.PERF_TEST":"SYSDBA.PERF_TEST"}@CREATE_INDEX:END:SUBOBJ=IDX_AGE:TIME=8 millisecond(s) [TASK]{table-"WUSHAN.PERF_TEST":"SYSDBA.PERF_TEST"}@CREATE_INDEX:END:SUBOBJ=IDX_NAME:TIME=8 millisecond(s) [TASK]{table-"WUSHAN.CLASS":"SYSDBA.CLASS"}@ADD_UNIQUE:END:SUBOBJ=UK_CLASS_GRADE_NO:TIME=8 millisecond(s) [END]执行完成. 任务总数:17, 完成:17, 出错:0, 取消:0, 耗时:20秒593毫秒
迁移完的验证(DM 端 count 对比 Oracle 端 count,不是看统计信息):
表 DM 端行数 Oracle 端 count(*) CLASS 60 60 PERF_TEST 1010 1010 STUDENT 1101099 1101099 STUDENTSCORES 1000 1000
主键、唯一约束(UK_CLASS_GRADE_NO)、索引(IDX_AGE/IDX_NAME)全部自动带过来了,Oracle 的 NUMBER/VARCHAR2 类型原样落到 DM(DM 本来就在兼容 Oracle 语法)。110 万行的 STUDENT 数据搬运 14 秒,加建表加索引全程 20.59 秒——这个速度对一台 8 核的测试机来说够看。
DTS 到底是怎么一步步搬的:跟着任务日志走一遍
命令行跑起来是个黑盒,但 DTS 会在 dts_transform.log 里按任务把每一步写清楚。把上面那次全绿的日志按时间轴摊开,能看出一套完整的"先分析、再建表、后导数据、最后补约束"流水线:
19:44:51.318 建立连接 [SOURCE] jdbc:oracle:thin:@//10.168.1.250:1521/orcl([JDBC]:21.9 [DB]:11.2) [DEST] jdbc:dm://127.0.0.1:5236([JDBC]:9.1 [DB]:9.1) [START]{11}START TRANSFORM... 19:44:51.323 阶段一:分析源库(两件事并行启动) [TASK]@ANALYZE_TABLE:START ← 读源库对象清单、表结构 [TASK]@ANALYZE_DEPEND:START ← 分析对象依赖(谁引用谁) 19:44:56.513 阶段二:目标端建表(4 张表顺序执行,每张十几毫秒) [TASK]{WUSHAN.CLASS:SYSDBA.CLASS}@CREATE_TABLE:START ... 4 张表全部 CREATE_TABLE:END [TASK]@ANALYZE_TABLE:END:TIME=5 second(s) 190 millisecond(s) ← 分析完,耗时大头在读 Oracle 字典 19:44:56.589 阶段三:导数据(4 张表并行 COPY_DATA) [TASK]{WUSHAN.CLASS:SYSDBA.CLASS}@COPY_DATA:START [TASK]{WUSHAN.STUDENT:SYSDBA.STUDENT}@COPY_DATA:START ← 大表小表一起启动 [TASK]{WUSHAN.PERF_TEST:...}@COPY_DATA:END:COPIED=1010:TIME=148ms [TASK]{WUSHAN.STUDENTSCORES:...}@COPY_DATA:END:COPIED=1000:TIME=29ms [TASK]{WUSHAN.CLASS:...}@COPY_DATA:END:COPIED=60:TIME=25ms ← 小表都是毫秒级 [TASK]{WUSHAN.STUDENT:...}@COPY_DATA:END:COPIED=1101099:TIME=14s ← 110 万行是大头 19:45:10.618 阶段四:补主键(数据导完才加,插入更快) [TASK]{...CLASS...}@ADD_PRIMARY_KEY:END:TIME=13ms [TASK]{...STUDENT...}@ADD_PRIMARY_KEY:END:TIME=1s 242ms ← 110 万行建主键索引花 1.2 秒 19:45:11.885 阶段五:补索引和唯一约束 [TASK]{PERF_TEST}@CREATE_INDEX:END:SUBOBJ=IDX_AGE:TIME=8ms [TASK]{PERF_TEST}@CREATE_INDEX:END:SUBOBJ=IDX_NAME:TIME=8ms [TASK]{CLASS}@ADD_UNIQUE:END:SUBOBJ=UK_CLASS_GRADE_NO:TIME=8ms [TASK]@ANALYZE_DEPEND:END:TIME=20 second(s) 589 millisecond(s) ← 依赖分析收尾,全程后台跑 19:45:11.912 汇总 [END]执行完成. 任务总数:17, 完成:17, 出错:0, 取消:0, 耗时:20秒593毫秒
几个值得注意的点:
- 建表阶段只花了几十毫秒,真正的耗时在 ANALYZE(读 Oracle 数据字典)和 STUDENT 的 14 秒数据搬运。ANALYZE_DEPEND 从启动就挂在后台,一直到最后一个约束建完才 END——它统计的 20.59 秒其实是整个迁移的墙钟时间。
- 数据是"先导、后加主键":4 张表的 COPY_DATA 一起 START,说明 DTS 对多表是并行搬运的;主键、索引全部放在数据导完之后再加,这样批量插入不用每行维护索引,是典型的"先灌数据后建索引"策略。
- 每个对象都带着
WUSHAN.表名 → SYSDBA.表名的映射:源模式 WUSHAN、目标模式 SYSDBA,日志里一目了然。
顺带一个坑外发现:Oracle 的 all_tables.num_rows 统计信息显示 STUDENT 是 1101100 行,实际 count(*) 是 1101099——统计信息虚高 1 行。别拿 num_rows 当迁移验收标准,对账必须用 count(*)。
三个坑的完整排查过程
下面三个坑按时间顺序排。每个都先给现象和原始报错,再讲我是怎么一步步定位的,最后是验证过的修法。前两个坑的教训其实是同一个:别拿 GUI 时代的经验套命令行工具。
第一个坑:“网络通信异常”——disql 连得上,DTS 连不上,问题不在网络
现象。 第一次跑迁移,命令用的是直接传参:
cd $DM_HOME/tool
./dts_cmd_run.sh CONFIG FILE=/tmp/b3_v5.xml DESCRYPT_PASSWORD=0
先交代这份 b3_v5.xml 是哪来的。服务器没有图形界面,GUI 版 DTS 起不来,没人帮你"选表、点下一步"生成标准配置——这份 XML 是我用 vi 手写的。说实话,第一版是凭对配置结构的理解拼的:根节点 TransformTask、两段连接信息、一个对象清单。但"凭理解"不是终点,踩完三个坑之后我把 DTS 主程序 com.dameng.dts_9.0.0.jar 里的配置解析类反编译出来逐字段核对了一遍——哪些蒙对了、哪些写冗余了、每个值为什么这么填,现在全部有源码出处。按字段拆开讲:
字段名不是猜的,是解析代码里的字符串字面量。 BaseConfiguration.fillSession 按名字逐个读子节点:
// BaseConfiguration.java,fillSession 内
value = this.xmlReader.getElementValue(pageNode, "Server"); // L153
port = this.xmlReader.getElementValue(pageNode, "Port"); // L161
value = this.xmlReader.getElementValue(pageNode, "AuthType"); // L162
charset = this.xmlReader.getElementValue(pageNode, "Charset"); // L177
jdbcURL = this.xmlReader.getElementValue(pageNode, "URL"); // L222
user = this.xmlReader.getElementValue(pageNode, "User"); // L225
password = this.xmlReader.getElementValue(pageNode, "Password"); // L226
同一个类里还有 GUI 保存配置的写盘代码,addChildElement("Server")、addChildElement("Port")……用的是同一套节点名。读和写同构,手写配置照抄这套字段名就不会错。
根节点的 transformer="11" 有三重证据:迁移日志先出现 [START]{11};反编译 Oracle→DM 方向的插件类 com.dameng.dts.plugin.oracle.dm7.transformer.Transformer,就一行 public int getId() { return 11; };调度链上 ConfigurationHandler 拿这个数字去 TransformerRegistry.getTransformer(int),按 getId() 匹配引擎。三面对上——11 就是 Oracle→DM 迁移引擎的注册编号,写别的数字工具直接找不到引擎。
几个取值的依据:
useDefaultURL="false"+ 直接给<URL>:源码里 URL 是双轨的——开关关着直接读<URL>(L222),开着才用<Server>/<Port>现拼。v5 两个都写了,Server/Port 属于冗余但无害。AuthType=0:按整数解析(L165),0 是用户名+密码常规认证;非数字才走布尔兜底(true→3)。Charset=UTF-8:只当会话字符集用,源和目标都是 UTF-8,这条是蒙对的。
密码这段最讲究:解析代码里解密开关默认是开的(private boolean descrypt = true;),只对非空密码生效。手写配置放明文,不显式声明"别解密",密码就会被当密文解成一串乱码——命令行里的 DESCRYPT_PASSWORD=0 就是这个声明。后来实测还发现个反直觉的点:直接传参方式下不带这个参数也能跑通明文配置,给 1 反而直接退回帮助界面——默认行为在不同入口下不一致,别赌,明文就显式给 0。
当时对"对象清单"这段最没底,按 GUI"对象"页签的字面意思包了个 <Object>,而且第一版只挂了 CLASS 一张表——先打通链路再扩量。这个写法对不对,手上没有任何标准配置可以对照,伏笔在坑三爆掉。v5 完整内容如下(密码打码,复现时换成自己的):
<TransformTask transformer="11" name="B3_ORACLE_DM" version="1.0"> <Source type="db" useCustomDriver="false" useDefaultURL="false"> <Server>10.168.1.250</Server> <Port>1521</Port> <URL>jdbc:oracle:thin:@//10.168.1.250:1521/orcl</URL> <AuthType>0</AuthType> <User>system</User> <Password>********</Password> <Charset>UTF-8</Charset> </Source> <Destination type="db" useCustomDriver="false" useDefaultURL="false"> <Server>127.0.0.1</Server> <Port>5236</Port> <URL>jdbc:dm://127.0.0.1:5236</URL> <AuthType>0</AuthType> <User>SYSDBA</User> <Password>********</Password> <Charset>UTF-8</Charset> </Destination> <Object> ← 按直觉写的,坑三会证明它错 <TransformItem id="1" type="TABLE" sourceSchema="WUSHAN" destSchema="SYSDBA" source="CLASS" destination="CLASS" isDefinitionAutoGenerated="true" customColumnMap="false"/> </Object> </TransformTask>
配置交代完,回来说现象。输出的不是迁移日志,是一坨堆栈:
迁移/tmp/b3_v5.xml... 解析迁移的配置文件/tmp/b3_v5.xml... java.sql.SQLException: 网络通信异常 2026-09-09 19:15:25 [com.dameng.dts.cmd.tool.Tool] [ERROR] error java.lang.RuntimeException: java.sql.SQLException: 网络通信异常 at com.dameng.dts.core.session.DTSSession.newConnection(DTSSession.java:318) ~[com.dameng.dts_9.0.0.jar:?] at com.dameng.common.persistence.session.Session.connect(Session.java:614) ~[com.dameng.common.persistence_9.0.0.jar:?] at com.dameng.dts.core.session.DTSSession.loadDriverAndConnect(DTSSession.java:259) ~[com.dameng.dts_9.0.0.jar:?] ... Caused by: java.sql.SQLException: 网络通信异常 at dm.jdbc.dbaccess.DBError.throwSQLException(DBError.java:58) ~[?:?] at dm.jdbc.driver.DmdbCSI.startupServer(DmdbCSI.java:304) ~[?:?] at dm.jdbc.driver.DmdbConnection.<init>(DmdbConnection.java:567) ~[?:?] at dm.jdbc.driver.DmDriver.connect(DmDriver.java:58) ~[?:?] at com.dameng.dts.driver.DriverAdapter.connect(DriverAdapter.java:40) ~[com.dameng.dts_9.0.0.jar:?] ...
第一反应是端口,结果被秒打脸。 DM 服务监听正常,本机三个地址全通:
$ ss -tlnp | grep 5236 LISTEN 0 128 *:5236 *:* users:(("dmserver",pid=278092,fd=4)) $ timeout 1 bash -c '</dev/tcp/127.0.0.1/5236' && echo OPEN 127.0.0.1:5236 OPEN localhost:5236 OPEN 10.168.1.175:5236 OPEN
再看 dm.ini,LISTEN_IP 是空的(默认监听所有地址),也不存在只放行某个 IP 的问题。最后用 disql 直接登:
服务器[localhost:5236]:处于普通打开状态 登录使用时间 : 2.315(ms) disql V9
同一台机器、同一个账号密码、同样的 JDBC URL,disql 能连,DTS 不能连。 到这基本可以断定:不是网络问题,是 DTS 自己的连接链路上有东西不对。
把堆栈当线索读。 报错最底下几行是真正的根因:
Caused by: java.sql.SQLException: 网络通信异常 at dm.jdbc.driver.DmdbCSI.startupServer(DmdbCSI.java:304) ~[?:?] at dm.jdbc.driver.DmDriver.connect(DmDriver.java:58) ~[?:?]
注意类名:dm.jdbc.driver.DmDriver——dm. 开头,这是 DM6 时代的驱动类名(DM8 之后是 dm8.jdbc.driver.DmDriver,DM9 装在 dropins 里的新驱动也是 dm8. 一族)。DTS 用的驱动类名是 6 代的,可目标服务器是 DM9。6 代驱动去连 9 代服务器,TCP 能通,但握手协议对不上,JDBC 驱动层就把这口锅甩给"网络通信异常"四个字。
验证驱动猜想。 DM9 的 DTS 驱动目录在 $DM_HOME/tool/dropins/com.dameng/plugins/com.dameng.jdbc.drivers/,里面躺着两个 DM 驱动:
-rw-r--r-- 1 dmdba dinstall 667457 4月 21 09:01 Dm6JdbcDriver.jar # 6 代驱动 -rw-r--r-- 1 dmdba dinstall 1664308 5月 9 14:14 DmJdbcDriver.jar # 9 代驱动(9.1.0.26)
DmJdbcDriver.jar 的 Manifest 写得明明白白:
Implementation-Title: Dameng JDBC driver classes for use with JDK1.8 Implementation-Version: - 9.1.0.26 - Production Driver-name: dm.jdbc.driver.DmDriver Build-Time: 2026.05.09
驱动类的名字两家都叫 dm.jdbc.driver.DmDriver(6 代和 9 代在类名上没换),但 DTS 默认加载的是老的那个 jar。
坐实"默认驱动指到 6 代":反编译 DTS 源码。 光猜不够,我直接把 DTS 主 jar 里的驱动加载器反编译出来看:
# 从 com.dameng.dts_9.0.0.jar 抽出 DriverLoader.class,用 CFR 反编译成 Java
java -jar cfr-0.152.jar \
com/dameng/dts/driver/DriverLoader.class --outputdir ./src
反编译结果里三个常量把代际关系写得清清楚楚:
private static final String DM6_KEY = "dm.jdbc.driver.DmDriver"; // 6 代类名
private static final String DM7_KEY = "dm7.jdbc.driver.DmDriver"; // 7 代类名
private static final String DM_KEY = "dm8.jdbc.driver.DmDriver"; // 8/9 代类名
继续往下翻,jar 文件名到驱动类名的映射更直接——Dm6JdbcDriver.jar 这个文件名被绑到了 6 代类名上:
if (fileName.equalsIgnoreCase("Dm7JdbcDriver.jar")) {
defaultDriverFiles.put(DM7_KEY, file);
} else if (fileName.equalsIgnoreCase("DmJdbcDriver.jar")) {
defaultDriverFiles.put(DM_KEY, file); // 9 代驱动挂在 dm8.jdbc... 名下
} else if (fileName.equalsIgnoreCase("Dm6JdbcDriver.jar")) {
defaultDriverFiles.put(DM6_KEY, file); // 6 代驱动挂在 dm.jdbc... 名下
}
再看加载逻辑 loadDriver → loadDefaultDriver → defaultDriverFiles.get(类名):DTS 内部用目标库类型去查驱动,DM 类型对应的默认类名是 dm.jdbc.driver.DmDriver(6 代那个 key),于是每次都命中 Dm6JdbcDriver.jar——目录里明明躺着 9 代驱动,它看都不看。这就是"硬编码":DTS 出厂时把 DM 默认驱动写死成 6 代类名了。
用实验锁死结论。 光读代码还不够,做个对照:把 Dm6JdbcDriver.jar 临时移走再跑,看它什么反应:
=== 移走 Dm6JdbcDriver.jar 后重跑 === 迁移/tmp/b3_v6.xml... 解析迁移的配置文件/tmp/b3_v6.xml... java.lang.RuntimeException: No default drivers found. at com.dameng.dts.driver.DriverLoader.loadDefaultDriver(DriverLoader.java:685)
“默认驱动找不到”——因为它按 dm.jdbc.driver.DmDriver 找,唯一挂着这个名字的 Dm6JdbcDriver.jar 被挪走了,9 代的 DmJdbcDriver.jar 挂在 dm8.jdbc.driver.DmDriver 名下,不在查找范围内。这个报错反过来证明了前面的判断:DTS 找 DM 驱动只认 6 代那个 key。
修法。 目录里那个"6 代文件名"的位置,实际加载时被当成默认驱动,那就把 9 代驱动的内容放到这个位置:
cd $DM_HOME/tool/dropins/com.dameng/plugins/com.dameng.jdbc.drivers
cp Dm6JdbcDriver.jar Dm6JdbcDriver.jar.orig # 原文件留后手
cp DmJdbcDriver.jar Dm6JdbcDriver.jar # 9.1.0.26 顶到默认加载位
替换后再跑,网络通信异常消失,迁移正常执行。这个坑不写出来,光看"网络通信异常"四个字,够人排查一下午——而且排查方向大概率是防火墙、监听地址这些网络项,全是死路。
第二个坑:命令"敲下去没反应",只剩一屏帮助——stdin 和传参是两回事
现象。 驱动换好之后,我想把命令整理成"一条命令跑完"的脚本。一开始图省事,用管道把命令喂给 dts_cmd_run.sh:
echo "CONFIG FILE=/tmp/b3_v5.xml DESCRYPT_PASSWORD=0" | ./dts_cmd_run.sh
结果它没跑迁移,打印了一屏"请输入使用方式"的帮助就退出了:
请输入使用方式: HELP :打印帮助信息 EXIT :退出 SHOW_I [n] :进度显示时间间隔(单位:秒),默认10s打印一次进度 ------------------------------------- support transform/compare/estimate/generate ------------------------------------- CONFIG :DTS迁移配置文件(配置文件建议使用DTS工具自动生成,支持所有DTS支持的迁移方式) COMPARE_CONFIG :DTS对比配置文件(配置文件建议使用DTS工具自动生成,支持所有DTS支持的对比方式) ... Total time: .262 seconds
注意最后那行:Total time: .262 seconds——0.262 秒,啥也没干就退出了。报错都没有,连"参数不对"都不说。
定位。 试了几种喂法都是同样结果后,我意识到问题可能出在"命令是怎么给进去的"。dts_cmd_run.sh 有两种用法:
- 交互模式:不带参数直接跑,然后手动敲
CONFIG FILE=xxx,像 sqlplus 一样一行一行来; - 批处理模式:把整个命令作为命令行参数一次性传进去,跑完自动退出。
我用的 echo ... | ./dts_cmd_run.sh 属于第一种的变体——程序在等交互输入,管道只喂了一行,它读完发现没有后续命令(EOF),就把帮助打出来退出了。"命令敲下去没反应"不是参数写错,是命令给错了地方。
验证一下:直接传参,同一个配置文件立刻正常跑:
./dts_cmd_run.sh CONFIG FILE=/tmp/b3_v5.xml DESCRYPT_PASSWORD=0
# 迁移/tmp/b3_v5.xml... 解析迁移的配置文件... 开始执行迁移...
顺带把 CONFIG 命令的完整参数摸清楚(交互模式里敲 CONFIG 回车,它会打印用法):
请输入命令: 格式: CONFIG KEYWORD=VALUE 例程: CONFIG FILE=/OPT/DM_DM.XML DESCRYPT_PASSWORD=0 ----------------------------------------------- FILE : DTS迁移配置文件路径,例如: FILE=/opt/1.xml DESCRYPT_PASSWORD : 0:不需要, 1:需要, 默认值: 1 (DTS默认生成的配置文件登录密码都是加密的,所以需要解密, 但如果手动编辑过配置文件,一般给明文,这时就不需要解密) REPORT : EXCEL报告导出路径,指定目录 HTML_REPORT : HTML报告导出路径,指定目录 LOG : 日志导出路径,指定目录 ERR_LOG : 错误日志导出路径,指定目录 SHOW_I : 进度显示时间间隔(单位:秒),默认10s打印一次进度 DATA_TYPE_MAP_FILE: 类型映射配置文件路径,默认为FILE同目录下的datatype.xml ERR_SQL : 迁移错误SQL导出路径,指定目录 ERR_OBJECT : 导出迁移出错的对象的路径,指定目录
DESCRYPT_PASSWORD 的语义在第一个坑里已经拿源码说清了:它是"密码是否按密文解密"的开关,源码里默认开。实测对照(都在这台 175 上做的):直接传参方式下,不带参数、给 0,明文配置都能跑通;给 1 会直接退回帮助界面。别赌默认行为——手写明文配置就老老实实带 DESCRYPT_PASSWORD=0,不赌默认行为。
这个坑的教训:用命令行工具前先分清它支持批处理还是交互,echo | 工具 这种姿势对"从 stdin 读命令"的程序会静默失效。看到"退回帮助界面 + Total time 零点几秒",先怀疑命令没被真正执行。
第三个坑:ANALYZE_TABLE 直接 NPE,真凶是 XML 节点名
现象。 驱动和调用方式都对了,配置却还是跑不通。这次命令执行了、连接也建了,但死在第一个分析阶段:
[SOURCE] jdbc:oracle:thin:@//10.168.1.250:1521/orcl([JDBC]:21.9 [DB]:11.2) [DEST] jdbc:dm://localhost:5236([JDBC]:9.1 [DB]:9.1) [START]{11}START TRANSFORM... [TASK]@ANALYZE_TABLE:START [TASK]@ANALYZE_DEPEND:START [ERROR] 2026-09-09 19:33:10.442> [TASK]@ANALYZE_TABLE:FAIL:TIME=363 millisecond(s) java.lang.NullPointerException at com.dameng.dts.plugin.support.task.BaseAnalyzeTransformTask.createItemsFromSelected(BaseAnalyzeTransformTask.java:654) at com.dameng.dts.plugin.support.task.BaseAnalyzeTransformTask.createItems(BaseAnalyzeTransformTask.java:443) at com.dameng.dts.plugin.support.task.BaseAnalyzeTask$CreateItemThread.run(BaseAnalyzeTask.java:838) ... [END]执行完成. 任务总数:2, 完成:1, 出错:1, 取消:0, 耗时:374毫秒
报错解读。 堆栈指向 BaseAnalyzeTransformTask.createItemsFromSelected ——“create items from selected”(从选中的对象创建迁移条目)。ANALYZE_TABLE 是 DTS 读取"要迁哪些对象"的阶段,在这里 NPE,基本等于它没从配置里解析出任何对象清单。连接是通的(SOURCE/DEST 都正常打印了),问题出在配置文件的对象列表上。
找错过程。 真凶就是坑一开头贴过的那份 v5 配置——对象清单用了 <Object> 包裹,按 GUI"对象"页签的字面意思猜的:
<Object> ← 直觉写的,错了 <TransformItem id="1" type="TABLE" sourceSchema="WUSHAN" destSchema="SYSDBA" source="CLASS" destination="CLASS" isDefinitionAutoGenerated="true" customColumnMap="false"/> </Object>
为了确认 DTS 到底认什么节点名,我反编译了配置解析类 com.dameng.dts.core.config.BaseTransformerConfiguration,找到填充对象清单的那段:
public boolean fillPage(IPluginer transformer, String pageName, boolean runtime) {
...
Object pageNode = null;
pageNode = "Object".equals(pageName)
? this.xmlReader.getNode(rootElement, "TransformItems") // ← 认的是 TransformItems
: this.xmlReader.getNode(rootElement, pageName);
if (pageNode == null) {
return false; // 找不到直接返回
}
...
}
这段代码信息量很大:DTS 内部的对象清单节点名是 TransformItems,所谓 <Object> 只是 GUI 层的叫法,解析时它会把 “Object” 翻译成去读 <TransformItems> 节点。我的 XML 里写的是 <Object>,它去找 <TransformItems> 找不到,pageNode == null 直接返回 false——配置解析"成功但为空",后面 ANALYZE 阶段拿不到对象列表就 NPE 了。
再往下翻,每个具体对象的节点也确认了:
List transformItemNodeList = this.xmlReader.getNodeList(pageNode, "TransformItem");
对象清单是 <TransformItems>(复数,容器),每个对象是子节点 <TransformItem>(单数),属性有 id/type/sourceSchema/destSchema/source/destination/isDefinitionAutoGenerated 等——跟 GUI 里"添加对象"填的那行字段一一对应。
修法。 把 <Object> 换成 <TransformItems>:
<TransformItems> <TransformItem id="1" type="TABLE" sourceSchema="WUSHAN" destSchema="SYSDBA" source="CLASS" destination="CLASS" isDefinitionAutoGenerated="true" customColumnMap="false"/> </TransformItems>
重跑,ANALYZE_TABLE 正常通过,后面 CREATE_TABLE、COPY_DATA 一路绿灯。
这个坑的教训:DTS 的 XML 配置结构是"GUI 术语 ≠ XML 节点名",写之前最好先反编译看一眼解析类或者找一个官方样例,别拿界面上的词去猜 XML。
能复现的完整配置
贴一份可直接改用的完整配置(4 张表全量,密码已打码)。除了 <TransformItems> 外注意三点:Source/Destination 各写 Server+Port+URL+账号;type="db" 表示库到库迁移;每个 <TransformItem> 是一张表,sourceSchema/destSchema 管模式映射:
<?xml version="1.0" encoding="UTF-8"?> <TransformTask transformer="11" name="B3_ORACLE_DM_FULL" version="1.0"> <Source type="db" useCustomDriver="false" useDefaultURL="false"> <Server>10.168.1.250</Server> <Port>1521</Port> <URL>jdbc:oracle:thin:@//10.168.1.250:1521/orcl</URL> <AuthType>0</AuthType> <User>system</User> <Password>your_pwd</Password> <Charset>UTF-8</Charset> </Source> <Destination type="db" useCustomDriver="false" useDefaultURL="false"> <Server>127.0.0.1</Server> <Port>5236</Port> <URL>jdbc:dm://127.0.0.1:5236</URL> <AuthType>0</AuthType> <User>SYSDBA</User> <Password>your_pwd</Password> <Charset>UTF-8</Charset> </Destination> <TransformItems> <TransformItem id="1" type="TABLE" sourceSchema="WUSHAN" destSchema="SYSDBA" source="CLASS" destination="CLASS" isDefinitionAutoGenerated="true" customColumnMap="false"/> <TransformItem id="2" type="TABLE" sourceSchema="WUSHAN" destSchema="SYSDBA" source="PERF_TEST" destination="PERF_TEST" isDefinitionAutoGenerated="true" customColumnMap="false"/> <TransformItem id="3" type="TABLE" sourceSchema="WUSHAN" destSchema="SYSDBA" source="STUDENT" destination="STUDENT" isDefinitionAutoGenerated="true" customColumnMap="false"/> <TransformItem id="4" type="TABLE" sourceSchema="WUSHAN" destSchema="SYSDBA" source="STUDENTSCORES" destination="STUDENTSCORES" isDefinitionAutoGenerated="true" customColumnMap="false"/> </TransformItems> </TransformTask>
运行命令:
cd $DM_HOME/tool
./dts_cmd_run.sh CONFIG FILE=/tmp/b3.xml DESCRYPT_PASSWORD=0
要迁整个模式就多写几个 <TransformItem>,或者研究下 DTS 的"按模式选择"配置(我这次是逐表指定的,更可控,日志里每张表的状态一目了然)。
翻车现场:目标库已有同名表,DTS 会硬插然后报主键冲突
第一次跑通后,我又做了一次"全量重迁"验证,结果 CLASS 表炸了:
[TASK]{table-"WUSHAN.CLASS":"SYSDBA.CLASS"}@COPY_DATA:FAIL:COPIED=0:TIME=43 millisecond(s) com.dameng.dts.plugin.support.task.TransformDataException: java.sql.BatchUpdateException: 违反表[CLASS]唯一性约束条件[PK_CLASS]
原因不复杂:CLASS 之前已经迁过一次,目标库里表和数据都在。DTS 不会自动清目标表,直接往里插,撞了主键。报告里的 errorDatas.js 连冲突行都给你记下来了:
var errorDatas=[{
tableName:'"SYSDBA"."CLASS"',
dataRow:'"1","高一","1","高一(1)班"," 2026-09-01 23:09:05"',
exception:'违反表[CLASS]唯一性约束条件[PK_CLASS]'
}]
结论就一条:重迁前先清理目标端同名对象,或者在 DTS 里配好"目标存在时的处理策略"。我图省事直接 DROP 了四张表再跑,结果就是上面那张全绿日志。
收尾
无界面环境做 Oracle→DM9 迁移,DTS 命令行完全够用:一条 dts_cmd_run.sh CONFIG FILE=... 搞定建表、导数据、建索引、加约束,110 万行 20 秒,行数零误差,还会自动生成 HTML/EXCEL 迁移报告(报告路径会打在执行日志里)。真正花时间的不是迁移本身,是开头那三个坑。
给准备动手的同学三句话:
- 先看 DTS 驱动目录里有没有新旧两套 DM 驱动——有就先备份,用 9 代驱动顶到 Dm6JdbcDriver.jar 的位置;
- 命令用直接传参
./dts_cmd_run.sh CONFIG FILE=xxx DESCRYPT_PASSWORD=0,别用echo |管道喂——看到"退回帮助界面 + Total time .262 秒"就是命令没被吃进去; - XML 对象清单节点写
<TransformItems>(容器)包<TransformItem>(对象),别写<Object>——那是 GUI 的叫法,解析器会翻译成去找 TransformItems,找不到就 NPE。
三条都避开,你的第一次无界面 DTS 迁移,大概率比我顺利。
#达梦数据库 #达梦同行者征文




