一条 DROP TABLE 把表和里面几百行数据一起删掉。只要有全备 + 连续归档,就能把库恢复到误删前的那一刻,在恢复库里验证无误,再把表导回生产。这篇的每一步都在单机库上真跑过,命令与输出一字不差。
视频为命令行实录渲染;所有输出取自单机测试库(192.168.31.14 / DM01:5236、恢复库 DR01:5237)真实执行结果。
| 你具备的条件 | 能做到的恢复 | 耗时量级 | 能救回什么 |
|---|---|---|---|
| 全备 + 连续归档 本篇主路线 |
RESTORE + RECOVER ... UNTIL TIME |
分钟级 | 精确到误删前一刻。备份之后、误删之前写入的数据,靠归档重放一样能救回来 —— 本次实测中备份后新增的 50 行全部恢复 |
| 只有全备,没有归档 | RESTORE 到备份点 | 分钟级 | 只能回到最后一次全备那一刻,备份之后的所有写入全丢 |
| 没有备份,但回收站开着 出厂默认是关的 |
FLASHBACK TABLE ... TO BEFORE DROP |
秒级 | 数据完整回来,但索引名与约束名会变成 BIN$ 前缀,要手工改名。见页末「另一条快路」 |
| 什么都没有 | — | — | 只能从业务侧(上游系统、日志、报表库)重建 |
左侧点一步,这里显示该步的完整命令与实测输出。点同一块可取消固定,按 Esc 也可取消。
先确认表是不是真的没了,再记下误删前的时间点。这个时间点是第 7 步 UNTIL TIME 的输入,必须在这一步就记下来,事后回想的时间通常不准。
-- 1) 确认表确实不存在了(返回 0 就是没了)
SELECT COUNT(*) AS EXISTS_CNT
FROM DBA_TABLES
WHERE OWNER = '<模式名>'
AND TABLE_NAME = '<表名>';
-- 2) 记下恢复目标时间点(误删前一刻)
SELECT TO_CHAR(SYSDATE,'YYYY-MM-DD HH24:MI:SS') AS T1 FROM DUAL;
LINEID EXISTS_CNT ---------- -------------------- 1 0 LINEID T1 ---------- ------------------- 1 2026-10-26 22:19:560 说明表已经不在数据字典里,任何"查表"手段都看不到它。接下来能救它的只有备份。
这一步决定你能走多远。三个都要查:归档开没开、归档文件还在不在、备份集有没有。少一样,恢复精度就打一个折扣。
-- 归档模式:Y=已开,N=未开
SELECT ARCH_MODE, STATUS$ FROM V$DATABASE;
-- 归档配置开关(1=启用 dmarch.ini)
-- 查看 dm.ini 与 dmarch.ini
grep -E "^\s*ARCH_INI" $DM_HOME/../dmdata/DAMENG/dm.ini
cat $DM_HOME/../dmdata/DAMENG/dmarch.ini
-- 已有的备份集与归档文件
ls -la <备份集目录>
ls -la <归档目录>
LINEID ARCH_MODE STATUS$ ---------- --------- ----------- 1 Y 4 ARCH_INI = 1 #dmarch.ini [ARCHIVE_LOCAL1] ARCH_TYPE = LOCAL ARCH_DEST = /home/dmdba/dmarch ARCH_FILE_SIZE = 128 ARCH_SPACE_LIMIT = 0 -rw-r--r-- 1 dmdba dinstall 315392 2026-10-26 22:10 ARCHIVE_LOCAL1_0x14DB70B5_EP0_2026-10-26_22-05-43.log -rw-r--r-- 1 dmdba dinstall 372736 2026-10-26 22:18 ARCHIVE_LOCAL1_0x14DB70B5_EP0_2026-10-26_22-10-19.log
ARCH_MODE = Y、归档目录里有连续文件、备份集存在 —— 三个条件齐了,才能做时间点恢复。
ARCH_MODE = N)。这时哪怕有全备,也只能恢复到最后一次全备那一刻,全备之后的所有改动全部丢失。所以"归档开了没"应该进日常巡检,而不是等出事才查。
在线全备,不需要停业务。注意:这里用的是 SQL 的 BACKUP 语句,不是用 dmrman 做备份 —— dmrman 的备份命令要求库处于 MOUNT 状态,OPEN 下会直接报 [-137]。
-- 在线全库备份(OPEN 状态即可执行)
BACKUP DATABASE FULL BACKUPSET '<备份集目录>';
backup database full backupset '/home/dmdba/bak/full_G'; executed successfully used time: 00:00:03.085 $ du -sh /home/dmdba/bak/full_G 16M /home/dmdba/bak/full_G $ ls /home/dmdba/bak/full_G full_G.bak full_G_1.bak full_G.meta16 MB / 3.1 秒(空库量级)。备份集格式与 dmrman 完全兼容 ——备份可以用 SQL 做,恢复照样用 dmrman,两者能对接。
DmAPService(DMAP 进程,4236 端口),没起来会报 [-7170] Bakres failed to connect DMAP;② 用 systemd 启动的实例如果没设 WorkingDirectory,备份会报 [-8086] the trace file [../log/...] is invalid,因为相对路径 ../log 解析到了无写权限的目录。
全备之后、误删之前,业务还在写。这部分数据不在备份集里,只能靠归档重放回来 —— 这正是"时间点恢复"相对"还原备份"的价值。
-- 备份完成后再写入一批数据(模拟正常业务)
INSERT INTO <模式名>.<表名>
SELECT LEVEL+200, 'CUST_'||LPAD(LEVEL+200,4,'0'), 1000+LEVEL+200
FROM DUAL CONNECT BY LEVEL <= 50;
COMMIT;
SELECT COUNT(*) AS CNT, SUM(AMT) AS SUM_AMT, MAX(ID) AS MAX_ID
FROM <模式名>.<表名>;
LINEID CNT SUM_AMT MIN_ID MAX_ID ---------- -------------------- ------- ----------- ----------- 1 250 281375 1 250备份集里只有 200 行(SUM=220100),现在表里有 250 行(SUM=281375)。多出的 50 行只能靠归档救。
本次实测最关键、也最容易漏的一步。归档文件是按 ARCH_FILE_SIZE 预分配的,没写满就不会闭卷;而 dmrman 只应用已闭卷的归档文件。不切换,恢复永远只回到备份点。
-- 强制把当前归档文件写完并闭卷,开启新的归档文件
ALTER DATABASE ARCHIVELOG CURRENT;
切换前: -rw-r--r-- 1 dmdba dinstall 134217728 2026-10-26 22:16 ..._22-10-19.log ← 预分配 128M,实际只写了 372K 切换后: -rw-r--r-- 1 dmdba dinstall 372736 2026-10-26 22:18 ..._22-10-19.log ← 闭卷,回落到真实大小 -rw-r--r-- 1 dmdba dinstall 8192 2026-10-26 22:18 ..._22-18-17.log ← 新归档文件开始写
RECOVER 会报 recover successfully,看起来一切正常,但恢复库里查出来还是备份时的 200 行,备份后的 50 行一行都没有。报"成功"不等于恢复到位,必须靠第 8 步的数据校验来确认。
建一个独立的恢复库,把全备还原进去。不要在生产库上直接 RESTORE —— 那会把生产库覆盖成备份那一刻,等于二次事故。
-- 1) 用 dminit 建一个空库(端口避开生产库的 5236)
./dminit path=<恢复库目录> DB_NAME=DAMENG INSTANCE_NAME=DR01 PORT_NUM=5237 \
SYSDBA_PWD=<口令> SYSAUDITOR_PWD=<口令>
-- 2) 把全备还原到恢复库(dmrman 必须用 CTLFILE 传命令)
echo "RESTORE DATABASE '<恢复库>/DAMENG/dm.ini' FROM BACKUPSET '<备份集目录>'" > restore.txt
./dmrman CTLFILE=restore.txt
RESTORE DATABASE '/home/dmdba/dmrestore/DAMENG/dm.ini' FROM BACKUPSET '/home/dmdba/bak/full_G' restore successfully. time used: 00:00:03.871
SYSDBA_PWD 和 SYSAUDITOR_PWD,漏一个就 fail to init db,而且它是逐个提示的 —— 补了 SYSDBA 才告诉你还缺 SYSAUDITOR。另外 dminit 的页大小等参数必须与源库一致,本例两边都是默认值。
把归档日志重放到第 1 步记下的时间点。UNTIL TIME 就是这一步的全部意义 —— 不带它,就会重放到最新状态,而"最新"里包含那次 DROP。
-- 恢复到指定的误删前时间点
echo "RECOVER DATABASE '<恢复库>/DAMENG/dm.ini' \
WITH ARCHIVEDIR '<归档目录>' \
UNTIL TIME '2026-10-26 22:19:56'" > recover.txt
./dmrman CTLFILE=recover.txt
EP[0]'s cur_lsn[50692], file_lsn[50692] RECOVER DATABASE '/home/dmdba/dmrestore/DAMENG/dm.ini' WITH ARCHIVEDIR '/home/dmdba/dmarch' UNTIL TIME '2026-10-26 22:19:56' recover successfully! time used: 457.850(ms)457 毫秒就重放完了 —— 归档量小。归档越多、跨越时间越长,这一步越慢。
恢复到新目录后,数据库的魔数(db_magic)与归档文件里记录的不一致,实例会拒绝启动。UPDATE DB_MAGIC 把它对齐,然后才能起库。
-- 对齐 db_magic
echo "RECOVER DATABASE '<恢复库>/DAMENG/dm.ini' UPDATE DB_MAGIC" > dbmagic.txt
./dmrman CTLFILE=dbmagic.txt
-- 启动恢复库实例后校验数据
SELECT COUNT(*) AS CNT, SUM(AMT) AS SUM_AMT, MAX(ID) AS MAX_ID
FROM <模式名>.<表名>;
RECOVER DATABASE '/home/dmdba/dmrestore/DAMENG/dm.ini' UPDATE DB_MAGIC recover successfully! time used: 00:00:01.101 LINEID CNT SUM_AMT MIN_ID MAX_ID ---------- -------------------- ------- ----------- ----------- 1 250 281375 1 250250 行 / SUM=281375 —— 与误删前的生产库完全一致,备份后新增的 50 行全部回来了。
[FATAL] Instance DR01 startup failed, execute 'recover database ... update db_magic' in dmrman. 找不到别的原因时,先看是不是漏了它。
恢复库里的数据是"证据",生产库还空着。用 dexp 从恢复库导出目标表,再用 dimp 导回生产库 —— 只回写这一张表,不动整库。
-- 1) 从恢复库导出目标表(FILE 必须给全路径)
./dexp USERID=SYSDBA/<口令>@localhost:5237 \
FILE=<全路径>/orders_recovered.dmp \
TABLES=<模式名>.<表名>
-- 2) 导入生产库(5236)
./dimp USERID=SYSDBA/<口令>@localhost:5236 \
FILE=<全路径>/orders_recovered.dmp \
TABLES=<模式名>.<表名>
-- 3) 生产库校验
SELECT COUNT(*) AS CNT, SUM(AMT) AS SUM_AMT, MIN(ID) AS MIN_ID, MAX(ID) AS MAX_ID
FROM <模式名>.<表名>;
table :ORDERS export terminate, total export 250 rows, size 5.266 KB terminate export success without warning [1/1]create table ORDERS import table ORDERS , has coped with 250 rows, size 5.266 KB terminate import success without warning LINEID CNT SUM_AMT MIN_ID MAX_ID ---------- -------------------- ------- ----------- ----------- 1 250 281375 1 250生产库回到 250 行 / SUM=281375,主键与索引随表一起重建。
FILE= 必须写全路径,用 FILE=x.dmp DIRECTORY=dir 的形式会静默结束、不产出文件也不写日志;② TABLES= 要写模式名.表名。另外 dimp 重建后索引名会变成系统生成名(如 INDEX33555512),如果业务或监控按索引名引用,需要手工 ALTER INDEX ... RENAME TO 改回来。
| 对比项 | 备份恢复(本篇主线) | 回收站闪回 |
|---|---|---|
| 前提 | 有全备 + 连续归档 | RECYCLEBIN=1(出厂默认 0) |
| 耗时 | 分钟级 | 秒级(实测 14 毫秒) |
| 精度 | 可指定到误删前任意时间点 | 只能回到 DROP 那一刻的整表状态 |
| 副作用 | 需要恢复库空间;dimp 后索引名变化 | 索引名/约束名变 BIN$ 前缀,必须改名 |
| 适用 | 有备份体系时的标准做法 | 回收站恰好开着时的运气路径 |
只能救到最后一次全备那一刻。全备之后写入的所有数据(本次实测里那 50 行)全部丢失,没有任何办法补回来 —— 因为它们只存在于 redo 日志里,而没开归档时这些日志会被循环覆盖。
所以判断顺序是:先查归档开没开,再决定期望值。这也是为什么"归档开了没"必须进日常巡检。
这次实测真踩到了,原因是归档文件没闭卷。归档按 ARCH_FILE_SIZE 预分配(本例 128 MB),没写满时不闭卷,而 dmrman 只应用已闭卷的归档文件 —— 于是备份之后的日志一条都没被重放,但命令本身仍然报成功。
解法:恢复前先执行 ALTER DATABASE ARCHIVELOG CURRENT; 把当前归档切出来。教训是:永远不要只看 recover 的返回,必须查恢复库里的实际数据。
技术上能启动、能查询,但不要这么干。恢复库是「误删前某一刻的静态快照」,它停在那个时间点,不接收新写入;你把它切上线,等于把整个库回退到误删前,误删之后所有业务的正常变更全部丢失,比丢一张表严重得多。
正确做法就是第 9 步:只把需要的那张表导出来,回写到生产库。
数据库魔数(db_magic)是库的唯一标识,写在控制文件和数据文件里,归档日志也带着它。把备份还原到一个新建的恢复库时,新库的魔数与归档文件里记录的不一致,实例启动时校验失败直接退出。
RECOVER DATABASE ... UPDATE DB_MAGIC 的作用就是把这个标识对齐,让恢复库能够接受这份归档并正常启动。它不是可选项,是恢复到新目录的必经步骤。
不能直接照抄。本篇所有输出取自单机实例(DM01,192.168.31.14:5236;恢复库 DR01:5237)。集群环境下每个节点的归档、备份集来源、以及恢复到哪个节点上启动,都需要单独实测确认。
按以往经验,DPC 里实例级参数的查询要落到 MP 上做(BP 节点查部分 DPC_* 视图会报 [-3818])。这块等实测完再单独出一篇,不要在集群上按单机的直觉操作。
空间上要留出一份完整数据文件 + 归档的量:恢复库是独立目录,体积约等于生产库的数据量(本例恢复库从空库 + 16 MB 备份集还原后占 1 GB 量级)。归档目录要能覆盖"最后一次全备到现在"的全部日志。
时间上(本例实测):全备 3.1 秒、RESTORE 3.9 秒、RECOVER 0.46 秒、UPDATE DB_MAGIC 1.1 秒、dexp 0.13 秒、dimp 0.03 秒。整体分钟级,但这是空库量级;真实库的耗时主要取决于数据量和归档跨度。
把卡在第几步、报错原文、版本号发我就够判断了 —— 不用发 dm.ini 或任何配置文件。共性问题我会补进这篇,下一个人就不用再踩这个坑。
现象和上面几步都对不上?把已经试过的命令和结果说一下,我能判断是文档该补一句,还是版本或环境差异。真要贴参数,先把 IP、路径、口令替换掉。
想让答案别人也能查到?在 Gitee 仓库 提 issue,回复公开可查。