摘要:本文记录一次达梦 DM8 的极端灾难场景演练——控制文件被删、回滚段头部损坏、REDO 日志丢失,实例彻底无法启动。在常规恢复手段失效的情况下,使用 dmdul 工具仅凭
SYSTEM.DBF+MAIN.DBF两个数据文件,完整恢复了 10 张表共 1000 行数据和 10 个对象(序列、函数、存储过程、触发器、视图、同义词),并通过序列防重号、数据精度、大对象等抽查验证了恢复质量。文末附踩坑总结,希望对遇到类似灾难场景的同学有所帮助。
做 DBA 的都知道,数据库最怕的不是慢,而是起不来。控制文件丢失、REDO 损坏这类故障一旦发生,常规的备份还原流程往往无从下手——连实例都不认识了,谈何恢复?
本次实验的目的,就是构造这样一个"最坏情况",并验证 dmdul 离线恢复工具在极端场景下的能力。整个实验按四个条件闭环推进:
| 项目 | 说明 |
|---|---|
| 实验日期 | 2026-09 |
| 实验服务器 | 192.168.1.xxx(内网,RHEL 7.6,x86_64) |
| 数据库版本 | DM8(dmrman V8,备份集 V8.10.18.2),实例 DMSERVER |
| dmdul 版本 | v0.9.0(2026-08-22 构建) |
| DM 安装目录 | /data/dmdbms |
| 数据目录 | /dmdata/DAMENG(SYSTEM.DBF 256MB / MAIN.DBF 128MB / ROLL.DBF 128MB / TEMP.DBF 128MB) |
| 服务 | DmServiceDM.service(systemd 托管) |
| 归档 | LOCAL 归档至 /dmdata/dmarch |
| 连接方式 | SYSDBA@5236(密码已脱敏,通过 JDBC 连接) |
| dmdul 工作区 | /data/dmdul_work/snap(离线 DBF 快照副本) |
| 冷备份目录 | /dmdata/dmbak/DUL_TEST_COLD_20260827 |
| 恢复输出目录 | /dmdata/dmbak/DUL_RECOVER_20260827 |
恢复工具好不好,得有精确的"标尺"来量。我在 TESTDUL 用户下执行了 36 条 SQL,构造了10 张表 + 10 个其它对象,全部执行成功。
| 表 | 关键列 | 覆盖点 |
|---|---|---|
| T_TEST01 | INT PK, VARCHAR, DECIMAL(12,2), DATE, TIMESTAMP | 常规组合 |
| T_TEST02 | BIGINT PK, VARCHAR, NUMERIC(18,4), CHAR DEFAULT | 大整数 + 数值精度 |
| T_TEST03 | SMALLINT PK, CHAR(20), FLOAT | 短整数 + 浮点 |
| T_TEST04 | INT PK, VARCHAR, BLOB, CLOB | 大对象(行内小值) |
| T_TEST05 | INT PK, DATE, TIME, INT | 时间类型 |
| T_TEST06 | INT PK, VARCHAR, TIMESTAMP | 日志表 |
| T_TEST07 | INT PK, VARCHAR, INT | 触发器目标表 |
| T_TEST08 | INT PK, DECIMAL(20,6), INT, VARCHAR | 高精度数值 |
| T_TEST09 | INT PK, FK_ID, VARCHAR + 外键 FK_T09→T_TEST01 | 外键约束 |
| T_TEST10 | INT PK, VARCHAR(可空), INT | NULL + 边界值(±2^31) |
| # | 对象 | 说明 |
|---|---|---|
| 1 | SEQ_TEST01 | 正向序列 NOCACHE,已取值(nextval=3) |
| 2 | SEQ_TEST02 | START 1000 INCREMENT 2 CACHE 20 |
| 3 | SEQ_TEST03 | 反向序列 INCREMENT -1 MIN 1 MAX 100 |
| 4 | FUNC_TEST01 | 函数(A+B) |
| 5 | FUNC_TEST02 | 函数(字符串拼接) |
| 6 | PROC_TEST01 | 存储过程(插入日志) |
| 7 | PROC_TEST02 | 存储过程(OUT 参数统计) |
| 8 | TRG_TEST01 | 触发器(BEFORE INSERT,引用 :NEW 与序列) |
| 9 | V_TEST01 | 视图(WHERE AMOUNT > 5000,51 行) |
| 10 | SYN_TEST01 | 同义词(指向 T_TEST02) |
另外附带 10 个主键索引、10 个主键约束和 1 个外键 FK_T09。
为什么要这么设计? 序列取过值(nextval=3)是为了验证恢复后防重号;NULL 和 ±2^31 边界值考验数据还原精度;BLOB/CLOB 考验大对象;外键、触发器、同义词则考验 DDL 还原的完整度。后面每一项都逐一验证了。
systemctl stop DmServiceDM
# 全量冷拷贝至备份目录(DBF×4 + dm.ctl + dm.ini + dmarch.ini
# + REDO×2 + sqllog.ini + dm_service.prikey + ctl_bak)
cp -a /dmdata/DAMENG/* /dmdata/dmbak/DUL_TEST_COLD_20260827/
# 记录 sha256 校验和,供还原后比对
sha256sum /dmdata/dmbak/DUL_TEST_COLD_20260827/* > sha256.txt
# 1. 删除控制文件
rm -f /dmdata/DAMENG/dm.ctl
# 2. 损坏回滚段头部 64KB
dd if=/dev/urandom of=/dmdata/DAMENG/ROLL.DBF bs=1024 count=64 conv=notrunc
# 3. 删除 REDO 日志
rm -f /dmdata/DAMENG/DAMENG01.log /dmdata/DAMENG/DAMENG02.log
# SYSTEM.DBF / MAIN.DBF / TEMP.DBF 保持完好(供 dmdul 读取)
$ systemctl start DmServiceDM
Job for DmServiceDM.service failed ... Active: failed (Result: exit-code)
$ su - dmdba -c "timeout 10 /data/dmdbms/bin/dmserver path=/dmdata/DAMENG/dm.ini"
file dm.key not found, use default license!
Read ini error, name:CTL_PATH, value:/dmdata/DAMENG/dm.ctl
nsvr_ini_file_read failed, [code: -803]
实例因控制文件丢失无法启动,常规恢复流程失效——这正是本次实验要面对的场景。
dmdul 的红线之一是绝不在原始文件上操作,先复制一份:
mkdir -p /data/dmdul_work/snap cp /dmdata/DAMENG/SYSTEM.DBF /dmdata/DAMENG/MAIN.DBF /data/dmdul_work/snap/
DMDUL> set data_dir /data/dmdul_work/snap;
DMDUL> set system /data/dmdul_work/snap/SYSTEM.DBF;
DMDUL> set output_dir /dmdata/dmbak/DUL_RECOVER_20260827;
DMDUL> set data_format sql;
DMDUL> bootstrap;
关键输出(全部 status=OK):
[bootstrap] phase=metadata status=OK db_name="DAMENG" instance_name="DMSERVER" page_size=32768 charset="GB18030" case_sensitive=1 [bootstrap] phase=precheck name=SYSTEM.DBF status=OK bad=0 checksum=0 ← 物理预检 [bootstrap] stage=1 phase=anchor name=SYSOBJECTS mode=root-chain status=OK root=0/16 rows=1253 [bootstrap] stage=1 phase=anchor name=SYSINDEXES mode=root-chain status=OK root=0/256 rows=179 [bootstrap] stage=2 phase=dictionary name=SYSCOLUMNS mode=root-chain status=OK rows=4887 [bootstrap] stage=2 phase=extract name=SEQUENCE_STATE mode=runtime-page-slot status=OK pages=1 rows=3 ← 序列运行页直读 [bootstrap] phase=complete status=SUCCESS mode=standard-two-stage objects=1253 elapsed_ms=16411
字典重建结果:users=2, schemas=3, tables=12, columns=86, views=1, sequences=3, routines=5, triggers=1, synonyms=9。
再用 list user 盘点确认 TESTDUL 用户下的对象一个不少:10 表 + 1 视图 + 1 同义词 + 3 序列 + 1 触发器 + 2 函数 + 2 过程。
值得注意:
SEQUENCE_STATE是从序列运行页直接读出来的(runtime-page-slot),这一步是后面"防重号"验证的基础。
DMDUL> unload object TESTDUL; -- 10 个对象 DDL
DMDUL> unload user TESTDUL; -- 10 张表 DDL + 数据
导出统计:
objects: tables=10 views=1 sequences=3 routines=4 triggers=1 synonyms=1 unload user: rows exported: 1000 rows failed: 0 planned pages: 10 direct pages read: 10 fallback pages scanned: 0 fallback reason: none ← page plan 全直读,零回退
1000 行全部导出、0 行失败、页面计划全直读零回退,干净利落。
输出目录 /dmdata/dmbak/DUL_RECOVER_20260827/ 共 21 个文件:
| 文件 | 内容 |
|---|---|
| TESTDUL_objects.sql | 用户定义 + 10 表 DDL + 序列×3 + 函数×2 + 过程×2 + 触发器 + 视图 + 同义词 + PK×10 + 外键 FK_T09 |
| TESTDUL_T_TEST01~10_ddl.sql | 各表建表 DDL(含 STORAGE CLUSTERBTR) |
| TESTDUL_T_TEST01~10_data.sql | 各表 100 行 INSERT 数据(共 1000 行) |
| 检查项 | 结果 |
|---|---|
| 10 表各 100 行数据 | ✅ rows exported: 1000, rows failed: 0 |
| 数据精度 | ✅ NAME_001、100.5、DATE '2026-01-02'、TIMESTAMP '2026-01-01 08:01:00.000000' 精确 |
| NULL 与边界值 | ✅ T_TEST10 第 100 行 NULL, 2147483647 |
| BLOB / CLOB | ✅ HEXTORAW('00000001')、CLOB 内容 RPAD 完整 |
| 主键/外键 | ✅ 10 个 ALTER TABLE ... ADD PRIMARY KEY + FK_T09 |
| 序列(防重号) | ✅ SEQ_TEST01 START WITH 4(运行页恢复 LAST_NUMBER,而非初始值 1) |
| 反向序列 | ✅ SEQ_TEST03 INCREMENT BY -1 MINVALUE 1 MAXVALUE 100 |
| 触发器 | ⚠️ 源码中 :NEW.GEN_VAL 被解码为 :E4rIGEN_VAL(GB18030 源码解码瑕疵),需人工修正后再回灌 |
| 用户密码 | ⚠️ 无法还原原密码(达梦加密存储),dmdul 生成新密码(已脱敏)并写注释说明 |
两个 ⚠️ 说明 dmdul 的定位很清晰:导出的 DDL 必须人工审查后,才能在隔离测试库回灌。
dmdul 抢救出数据后,再把实例本身从冷备份完整还原:
# 1. 从冷备份还原全部文件
cp -a /dmdata/dmbak/DUL_TEST_COLD_20260827/{SYSTEM,MAIN,ROLL,TEMP}.DBF /dmdata/DAMENG/
cp -a /dmdata/dmbak/DUL_TEST_COLD_20260827/{dm.ctl,dm.ini,dmarch.ini,sqllog.ini,dm_service.prikey,DAMENG01.log,DAMENG02.log} /dmdata/DAMENG/
# 2. 属主修复(关键!root 拷贝会破坏属主,导致 dmap 写入失败)
chown -R dmdba:dinstall /dmdata/DAMENG/*
# 3. sha256 校验和比对:7/7 OK
# 4. 启动
systemctl start DmServiceDM → active
恢复后通过 JDBC 验证:
SEQ_TEST01.nextval = 4,与 dmdul 恢复的 START WITH 4 一致,防重号闭环验证通过;FUNC_TEST01(20,22) = 42,函数可用;至此,四个条件全部闭环:数据救回来了,实例也还原了,且数据无损。
# 的密码:# 是 disql 登录选项(#{})的语法前缀,形如 用户名/含#密码@host 的连接串会报"用法:CONN[ECT]"或登录失败(dexp 同样报"登录失败")。解决方案:改用 JDBC(jdbc:dm://host:port + 用户/密码参数)连接,或者干脆让密码避开 #。chown dmdba:dinstall。用 root 直接拷贝会破坏属主,导致 dmap 写入失败、实例起不来——这是高频坑。:NEW 可能被解码成 :E4r。回灌前需人工检查 SYSTEXTS 类源码对象。一句话:备份是底线,dmdul 是底线之上的"最后一根稻草"。再完善的备份体系,也建议提前了解这类离线抢救工具的边界和用法——真出事的时候,它就是救命的那一手。
附注:
- 本文由内部实验报告整理而成,服务器 IP、账号密码等敏感信息均已脱敏。
- 实验环境已在服务器上保留完整痕迹(冷备份、恢复输出、dmdul 字典与日志),可复现复盘。
原创内容,转载请注明出处。欢迎在评论区交流达梦灾恢实践~
文章
阅读量
获赞
