本文基于 DM8→DM8 测试环境,完整记录达梦 DMDRS(数据实时同步软件 V5.2.5.4)的部署与三大核心场景:全量装载+增量同步(3×100 万行)、运行中在线加表(OGG 类比)、DM→DM 升级迁移三步走。所有 IP、端口、口令均为示例值,请按实际环境调整。
数据实时同步是迁移割接、容灾建设、仓库集成的刚需。DMDRS 采用**源端捕获(CPT)+ 目标端执行(EXEC)**的架构:
| 端 | 模块 | 职责 |
|---|---|---|
| 源端 | CPT(cpt_dm8) | 读源库日志 → 全量装载 → 发送增量 |
| 目标端 | EXEC(exec_dm8) | 接收 → 应用入库 |
架构要点:
mgr_port 必须唯一、siteid 链路内唯一(源=1、目标=2)。💡 本文两个测试库:源端
192.168.x.223:5236,目标端192.168.x.224:5567(示例),DM8 均已开启归档与逻辑日志。
源端、目标端均以 dmdba 用户安装同一版本(V5.2.5.4):
./dmdrs_rev<版本号>_x86_rh6_64_<日期>.bin -i
# 选择语言 → e(English);安装目录 → /dmdata/dmdrs5
# 安装组件 → 1(Install DMDFM);Key 文件 → 2(Use the applied license)
# License 路径 → /dmdata/software/dmdrs<授权号>.key;数据库驱动路径 → 回车跳过
配置 DPI 环境变量(源/目标 ~/.bash_profile 追加后 source 生效):
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/dmdata/dmdbms/bin:/dmdata/dmdbms/drivers/dpi"
export DM_HOME="/dmdata/dmdbms"
export PATH=$PATH:$DM_HOME/bin
换 Key 操作(试用期/到期时):新 key 放到 bin 目录 → 控制台 CSL> update lic → CSL> lic 查看授权信息。
ALTER DATABASE MOUNT;
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE ADD ARCHIVELOG 'DEST=/dmdata/drs_tmp/source/arch, TYPE=LOCAL, FILE_SIZE=512, SPACE_LIMIT=102400';
ALTER DATABASE OPEN;
CALL SP_SET_PARA_VALUE(1,'RLOG_APPEND_LOGIC',1);
RLOG_APPEND_LOGIC 取值含义:
| 值 | 含义 | 说明 |
|---|---|---|
| 1 | UPDATE/DELETE 只记录主键列 | 无主键则记录全列 |
| 2 | 记录全部列 | 字段最全,推荐;目标为 Doris 时必须为 2 |
| 3 | UPDATE 只记更新列 + ROWID | 更精简 |
💡 开启后建议重启实例确认生效。
disql 用户名/口令@源库IP:端口
SET DEFINE OFF;
START /dmdata/dmdrs5/bin/scripts/ddl_sql_dm8.sql # 创建 DRS_$DDL_LOG 辅助表 + 触发器
⚠️ DDL 同步的是「表、索引、约束」等对象级 DDL,不含 CREATE USER。新增 schema 时目标端必须手工先建好同名用户,否则
CREATE TABLE 新模式.xxx同步过去会因模式不存在失败。
源端、目标端创建同名迁移用户(本次直接授 DBA,生产建议最小权限):
CREATE USER DMDRS IDENTIFIED BY "口令";
GRANT "RESOURCE","PUBLIC","VTI","SOI","SVI" TO "DMDRS";
GRANT CREATE SESSION TO "DMDRS";
-- GRANT DBA TO DMDRS; # 生产不建议给 DBA,按需授 SELECT / DML / 建表等
<?xml version="1.0" encoding="GB18030"?>
<drs>
<base>
<mgr_port>5345</mgr_port>
<siteid>2</siteid>
</base>
<exec>
<name>exec_dm8</name>
<login>
<dbtype>dm8</dbtype>
<server>192.168.x.224</server>
<user>DMDRS</user>
<pwd>******</pwd>
<port>5567</port>
</login>
<group>
<item><id>35</id><exec_policy>0</exec_policy>
<desc><table>DRS_TEST.*</table></desc></item>
</group>
</exec>
</drs>
<?xml version="1.0" encoding="GB18030"?>
<drs>
<base>
<mgr_port>5345</mgr_port>
<siteid>1</siteid>
</base>
<cpt>
<name>cpt_dm8</name>
<login>
<dbtype>dm8</dbtype>
<server>192.168.x.223</server>
<user>DMDRS</user>
<pwd>******</pwd>
<port>5236</port>
</login>
<send>
<ip>192.168.x.224</ip> <!-- 目标端 DMDRS 服务 -->
<port>5345</port>
<target_name>exec_dm8</target_name>
<map>
<item>DRS_TEST.*==DRS_TEST.*</item> <!-- 白名单映射 -->
</map>
</send>
</cpt>
</drs>
map 白名单 = 增量同步的入境门槛:
alter cpt_dm8 add map "JUNHUI.*==JUNHUI.*" + confirm 立即生效(见场景二);用 bin/service_template 生成服务脚本 bin/DrsService,修改 5 项关键参数:
INSTALL_HOME=/dmdata/dmdrs5
PROG_DIR=/dmdata/dmdrs5/bin
CONF_PATH=/dmdata/dmdrs5/bin/exec.xml # 源端改 cpt.xml
NEED_LIB_PATH=/dmdata/dmdbms/bin/
EXEC_PROG_NAME=drsvr
SERVICE_TYPE_NAME="drs server"
启动顺序:先目标端 → 再源端:
# 目标端(192.168.x.224)
cd /dmdata/dmdrs5/bin/ && ./DrsService start
# Starting DrsService: [ OK ]
# 日志确认:drs server is starting... / starting finished
# 源端(192.168.x.223,目标端就绪后再启)
cd /dmdata/dmdrs5/bin/ && ./DrsService start
控制台入口:./drcsl cpt.xml(源)/ ./drcsl exec.xml(目标)→ connect → show mode / start cpt_dm8。
⚠️ 启动顺序铁律:先目标 DMDRS 服务 → 再源 DMDRS 服务 → 控制台 start CPT。
验证目标:DRS_TEST 模式 T_DEPT / T_EMP / T_ORDER 三表(含主键、唯一键、外键)各 100 万行全量装载,约束完整同步。
CSL> alter cpt_dm8 set lsn 0
CSL[INFO]: 成功获取模块的起始SCN cpt name: cpt_dm8, LSN: 11081265
CSL> start
MGR[INFO]: 模块已经处于运行状态 module: cpt_dm8
# 方式 A:先建表结构(不含数据)
alter cpt_dm8 add table "sch.name='DRS_TEST'" create|index|foreign|unique_key
# 无 INSERT 掩码时 DRS 会提示确认
# 方式 B(本次最终采用):结构与数据一次性装载
alter cpt_dm8 add table "sch.name='DRS_TEST'" create|index|foreign|unique_key|insert
# 方式 C:结构已建成后仅补数据
alter cpt_dm8 add table "sch.name='DRS_TEST'" insert
掩码含义:
| 掩码 | 含义 |
|---|---|
| CREATE | 目标端自动建表 |
| INDEX | 建索引 |
| UNIQUE_KEY | 建唯一约束 |
| FOREIGN | 建外键 |
| INSERT | 装载数据 |
| DROP | 先删后建(避免表已存在,常用组合 DROP|CREATE|INSERT) |
CSL> show cpt_dm8 load table last
+------------+----------+----------+----------+-----------+------+----------+-------------+------+------+
| 模式名 | 表名 | 已装载行数 | 状态 | 开始时间 | 耗时 | 发送进度 | 速度 | 分组数 | 掩码 |
+------------+----------+----------+----------+-----------+------+----------+-------------+------+------+
| DRS_TEST | T_DEPT | 1000000 | COMPLETE | 2026-xx-xx | 13 s | 100 % | 73405 row/s | 1 | ... |
| DRS_TEST | T_EMP | 1000000 | COMPLETE | 2026-xx-xx | 13 s | 100 % | 72364 row/s | 1 | ... |
| DRS_TEST | RDER | 1000000 | COMPLETE | 2026-xx-xx | 13 s | 100 % | 74688 row/s | 1 | ... |
+------------+----------+----------+----------+-----------+------+----------+-------------+------+------+
结果解读:三表状态 COMPLETE、发送进度 100%、各 100 万行、装载速率约 7.3 万行/秒。
装载状态含义:
| 状态 | 含义 |
|---|---|
| LOADING | 正在装载 |
| COMPLETE | 已完成装载 |
| ERROR | 装载错误 |
| TOLOAD | 等待装载 |
| WAIT CONTINUE | 等待续传(断点续装 alter cpt continue load) |
| LOAD STOP / UNEXPECTED HALT | 正常停止 / 故障停止 |
-- ① 行数
SELECT 'T_DEPT' AS TBL, COUNT(*) AS CNT FROM "DRS_TEST"."T_DEPT"
UNION ALL SELECT 'T_EMP', COUNT(*) FROM "DRS_TEST"."T_EMP"
UNION ALL SELECT 'T_ORDER', COUNT(*) FROM "DRS_TEST"."T_ORDER";
-- 三表均 1000000
-- ② 约束(目标端 DBA_CONSTRAINTS 实测)
SELECT TABLE_NAME, CONSTRAINT_NAME, CONSTRAINT_TYPE FROM DBA_CONSTRAINTS
WHERE OWNER='DRS_TEST' ORDER BY TABLE_NAME;
-- T_DEPT PK_T_DEPT P / UK_T_DEPT_1 U ... T_EMP FK_T_EMP_1 R ... P/U/R 全齐
验证结论:源/目标三表行数一致、主键§/唯一键(U)/外键® 约束完整同步,全量装载 + 约束同步验证通过 ✔。
在链路已运行的情况下,新增 JUNHUI 模式下一张持续滚动写入的动态表(每秒插 1 行/每 10s 删 1 条/每 15s 改 1 行),验证不停机、不丢数在线纳入同步——等价于 Oracle OGG 的在线添加同步表。
⚠️ DDL 同步不建用户:
JUNHUI是新增模式,源端、目标端必须各手工创建一次,否则同步/装载因模式不存在失败。
-- ① 源端:建用户 + 造数权限
CREATE USER JUNHUI IDENTIFIED BY 口令;
GRANT CREATE SESSION, CREATE TABLE, UNLIMITED TABLESPACE TO JUNHUI;
GRANT SELECT ON JUNHUI.T_TEST_DYNAMIC TO DMDRS;
GRANT EXECUTE ON SYS.DBMS_LOCK TO JUNHUI;
-- ② 目标端:手工建同名用户
CREATE USER JUNHUI IDENTIFIED BY 口令;
GRANT CREATE SESSION TO JUNHUI;
-- GRANT INSERT, UPDATE, DELETE, SELECT ON JUNHUI.T_TEST_DYNAMIC TO DMDRS; -- 非 DBA 才执行
-- ③ 源端建表 + 造数存储过程
CREATE TABLE JUNHUI.T_TEST_DYNAMIC (
ID BIGINT PRIMARY KEY, NAME VARCHAR(64), AMOUNT NUMBER(12,2),
CHECK_TIME TIMESTAMP(6), REMARK VARCHAR(255));
CREATE OR REPLACE PROCEDURE JUNHUI.SP_GEN_TEST_DATA AS
v_id BIGINT := 0; v_sec INT := 0;
BEGIN
SELECT NVL(MAX(ID),0) INTO v_id FROM JUNHUI.T_TEST_DYNAMIC;
LOOP
v_sec := v_sec + 1; v_id := v_id + 1;
INSERT INTO JUNHUI.T_TEST_DYNAMIC VALUES(v_id,'NAME_'||v_id,
MOD(v_id,100000000)/100.00, SYSTIMESTAMP, 'I:'||v_sec); COMMIT;
IF MOD(v_sec,10)=0 THEN
DELETE FROM JUNHUI.T_TEST_DYNAMIC WHERE ID=(SELECT MIN(ID) FROM JUNHUI.T_TEST_DYNAMIC); COMMIT;
END IF;
IF MOD(v_sec,15)=0 THEN
UPDATE JUNHUI.T_TEST_DYNAMIC SET AMOUNT=ROUND(AMOUNT+1.11,2),
CHECK_TIME=SYSTIMESTAMP, REMARK='U:'||v_sec
WHERE ID=(SELECT MAX(ID) FROM JUNHUI.T_TEST_DYNAMIC); COMMIT;
END IF;
DBMS_LOCK.SLEEP(1);
END LOOP;
END;
/
-- 后台运行
nohup disql JUNHUI/口令@源库IP:端口 -e "CALL JUNHUI.SP_GEN_TEST_DATA();" > /home/dmdba/junhui_gen.log 2>&1 &
CSL> alter cpt_dm8 add map "JUNHUI.*==JUNHUI.*"
CSL[INFO]: [INPUT CMD: alter cpt_dm8 add map "JUNHUI.*==JUNHUI.*"]
命令执行成功
CSL> alter cpt confirm
MGR[INFO]: 检测到修改映射规则操作,当前修改内容如上!请确认是否执行?(Y/N) Y
命令执行成功
CSL> show cpt_dm8 conf map # 确认生效
| CPT-SEND-SEND_MAP | MAP | TEST_USR.*==TEST_USR.* | ... |
| CPT-SEND-SEND_MAP | MAP | JUNHUI.*==JUNHUI.* | ... |
CSL> alter cpt_dm8 add table "sch.name='JUNHUI' and tab.name='T_TEST_DYNAMIC'"
MGR[INFO]: 获取到装载掩码组合 mask: |CREATE|INSERT|INDEX|TABLE|CHECK|GROUP
命令执行成功
CSL> show cpt_dm8 load table ALL
| 5 | JUNHUI | T_TEST_DYNAMIC | 1501 | COMPLETE | 2026-xx-xx 17:08:58 | 0 s | 1 | <-- 本次新增
装载结果:动态表 COMPLETE(1501 行,0 秒),装载完成后源端/目标端分别 COUNT(*) 验证一致,增量自动续上。
| Oracle OGG 传统做法 | DMDRS add table 一步完成 |
|---|---|
Extract 记录启动 SCN → INITLOAD 初始装载 → 目标端手配 ADD REPLICAT ... BEGIN SCN <n>,依赖 Extract 缓存衔接,手工对位点、易漏窗口 |
① 内部自动记录当前位点 → ② 全量装载 → ③ 装载期间增量缓存 → ④ 装载完自动补增量 → 追平后转入实时。无需手工对位点、无需停写 |
对应关系:alter cpt set lsn(等效 Extract 记 SCN)→ add table(等效 INITLOAD + 自动续增)→ add dict ... LSN(等效只追增量、不搬数据,目标表已手动初始化时用)。OGG 的 BEGIN SCN 手工衔接,在 DMDRS 里被 add table 内部化,天然无窗口。
典型场景:老库版本低/结构大改,目标端新库直接 face 升级迁移。流程:先停 CPT → 全量装载 → 建索引(CPT 保持停止)→ 再开启增量补数据,只搬数据、不产生重复。
CSL> show mode # 查看模块状态
-- ① 先把 CPT 停下来(升级迁移:装载前 CPT 就是停止状态)
CSL> stop cpt_dm8
CSL> show mode # 确认 STOP
-- ② 全量装载(INSERT 掩码,先删后建避免表已存在)
alter cpt_dm8 add table "sch.name='TEST_USR'" INSERT
-- 装载期间源端业务可照常写,CPT 已停,增量后续统一补
-- ③ 检查装载进度(装载任务独立调度,CPT 停止也能查)
CSL> show cpt_dm8 load table last / running / error
CSL> show cpt_dm8 load status last
装载不依赖 CPT 运行态。若 CPT 处于运行态,装载同时会启动增量抓取,变成「装载 + 增量」混合模式——扩容场景(场景二)正确,但升级迁移场景要求先全量后增量,所以要先进控制台 stop cpt_dm8 停掉,再 add table;装载完成、索引建好(期间 CPT 一直停止),最后 start cpt_dm8 补增量。
CSL> show mode # STOP
-- 目标库手工建索引(此时目标只有数据表,无索引,建索引最快)
-- 建索引期间源端业务可继续写;要装其他表直接 add table 即可
CSL> start cpt_dm8 # 重新启动 CPT
CSL> show mode # RUNNING
-- 启动后 CPT 自动从 checkpoint 续抓:暂停期间的源库变更批量补发到目标端
-- 验证:源端写一条 → 目标端查;show exec_dm8 lag 看延迟
💡 为什么先装载再建索引:目标端空表装载 100 万行只卖 13 秒(7.3 万行/秒),带索引装载会显著变慢;先全量空表装载(CPT 停机)→ 建索引(CPT 保持停止)→ 最后开增量,装载快、索引建得也快,业务影响窗口最小,增量补数时索引已就位。
| 命令 | 用途(实测输出形式) |
|---|---|
show cpt_dm8 load status last |
总体装载状态(各状态对象数、起止时间、掩码) |
show cpt_dm8 load table running |
正在装载的表实时进度(行数、速度) |
show cpt_dm8 load table last |
每张表最后一次装载结果(行数、状态、耗时) |
show cpt_dm8 load table error |
装载错误的表 |
show cpt_dm8 table [LIST\|ALL\|SCH.TAB] |
表操作统计(I/U/D 数),默认只显操作数>0 |
show exec_dm8 lag [GROUPID] |
目标端同步表延时(<1s 无延迟) |
show exec_dm8 ckpt / info |
目标端 LSN 位点 / 消费者接收信息 |
show cpt_dm8 state / cpt |
CPT 模块统计(读取/解析/发送 LSN、速率) |
show cpt_dm8 log online / arch |
在线 / 归档日志信息 |
alter cpt_dm8 stop load # 停止全部装载
alter cpt_dm8 stop table "ACCOP.AFC_DEVICE_INFO_ADD"."T1" # 停止指定表
alter cpt_dm8 delete load_record # 彻底清除装载历史
# 注:停止后目标端已装载数据不会删除
alter cpt_dm8 add dict "sch.name='JUNHUI' and tab.name='T_TEST_DYNAMIC'" LSN # 只追增量不装载
alter cpt_dm8 update dict "sch.name='..'" # 结构变更后更新
alter cpt_dm8 delete dict 模式名.表名 # 仅删源端字典
alter cpt_dm8 terminate dict 模式名.表名 # 删字典并通知目标清理
cp cpt_dm8 "sch.name='ACCOP' and tab.name='AFC_DEVICE_INFO_ADD'" INSERT
# cp = 纯全量,装载后不再追增量;add table = 装载 + 建字典 + 增量,二选一
stop cpt_dm8
alter cpt_dm8 set lsn 0
alter cpt_dm8 add table "sch.name='TEST_USR'" INSERT
show cpt_dm8 load table running / error / status last
stop cpt_dm8 # 装载完成停 CPT,期间建索引
start cpt_dm8 # 重启续抓增量
show mode # RUNNING
Q1:alter cpt set lsn 会影响到正在同步的表吗?
会!LSN 是 CPT 模块全局解析起点。运行中设成最新→延迟中未解析的日志被跳过(丢增量);设成历史→重复解析(主键冲突/重复行)。运行中在线加表不需要 set lsn(add table 内部自动对位)。set lsn 只用于:①新链路首启;②推倒重来(须 CPT 停止状态)。
Q2:当前 LSN 怎么查?
show cpt_dm8 state(读/解/发 LSN)、show cpt_dm8 log online|arch(日志位点)、show exec_dm8 ckpt(目标端接收位点)。
Q3:新 schema 目标端没有用户?
DDL 同步不建用户,必须先手工 CREATE USER JUNHUI...(源、目标两端),否则建表 DDL 同步/装载都会因模式不存在失败。
Q4:add table 后目标端报"表已存在"?
用带 DROP 的掩码先删后建:alter cpt_dm8 add table "..." DROP|CREATE|INSERT|INDEX;或先建结构再仅补数据 ... INSERT。
Q5:show 命令踩坑?
EXEC 的 SHOW 不带表名参数(表统计在 CPT 的 show cpt_dm8 table);show exec_dm8 table stat * 等臆造命令会报 DRS-6050。EXEC 延时=show exec_dm8 lag。
Q6:同步差数据怎么排查?
① 先确认源库归档 + RLOG_APPEND_LOGIC 已生效;② show exec_dm8 lag 看延迟;③ 两端 COUNT(*) 对比;④ 结合 DMDVS 静态校验 cmp job 定位差异表/行,动态表用 dcmp(依赖本条 DMDRS 链路)。
Q7:想只做全量迁移(不自动追增量)?
装载任务不依赖 CPT 运行态。CPT 运行时 add table 会同时启动装载和增量抓取(增量先缓存后补);升级迁移要"先全量后增量"分步走,就先把 stop cpt_dm8 停掉再 add table ... INSERT,装载完确认 COMPLETE,期间建索引,最后 start cpt_dm8 从 checkpoint 续抓增量补数。
| # | |
|---|---|
| 1 | 启动顺序铁律:先目标 DMDRS → 再源 DMDRS → 控制台 start CPT |
| 2 | 同步前提:源库开归档 + 逻辑日志(RLOG_APPEND_LOGIC);DDL 同步先跑 ddl_sql_dm8.sql |
| 3 | map 白名单是增量同步前提;运行中 add map+confirm 即时生效,但重启失效,需落盘 cpt.xml |
| 4 | 在线加表 = OGG 在线加同步表:add table 一条命令完成「记位点→全量→缓存增量→自动补」,无需停写、无需手工对 SCN |
| 5 | add table vs cp vs add dict:add table=装载+增量;cp=纯全量不建字典;add dict=只追增量不搬数据 |
| 6 | set lsn 是全局操作,运行中链路勿动;仅 CPT 停止且推倒重来 / 新链路首启时使用 |
| 7 | DDL 同步不建用户:新增 schema 目标端必须手工先建同构用户 |
| 8 | 伪装验证闭环:全量 100 万行≈13s(7.3 万行/s)+ 约束 P/U/R 全同步 + 动态表 COUNT 一致 + lag<1s |
| 9 | 控制台命令以 help 实测为准:EXEC SHOW 不带表名;表统计在 CPT;装载管理命令统一 alter cpt_dm8 ... |
| 10 | 升级迁移三步走:先 stop → 全量装载(CPT 停机状态)→ 建索引 → start 续抓增量,避免误入装载+增量混合模式 |
本文完整走通了 DMDRS 从部署到三大核心场景的闭环:
安装(源/目标同版本 + License)→ 库准备(归档 + 逻辑日志 + DDL 表)→ 用户授权
→ 配置 exec.xml / cpt.xml / map 白名单 → 先目标后源启动服务
→ 场景一:全量装载 + 增量(3×100 万,约束 P/U/R 全同步)
→ 场景二:运行中在线加表(JUNHUI 动态表,OGG 类比)
→ 场景三:升级迁移(全量→建索引→增量)
核心要点可归纳为三句话:
掌握这套流程,无论新建链路、运行中扩容还是升级迁移,DMDRS 都能快速上手,后续还可接 DMDVS dcmp 动态校验对同步结果做一致性把关。
文章
阅读量
获赞
