表空间的数据文件被 rm 掉之后,在实例没有重启的前提下,用进程还在持有的文件句柄把文件原样捞回来,再把表空间恢复上线。
.DBF 用 rm 删掉,
靠的是进程还持有文件句柄 —— 命门是实例千万不能重启,重启后句柄失效、文件彻底找不回。DROP TABLE(SQL 层删表、数据文件还在),请走
《达梦利用备份恢复误删表 · 交互版》
两类事故的前提完全相反,走错页面会耽误抢救时机。
视频为命令行实录渲染;所有输出取自单机测试库真实执行结果。
左侧点一步,这里显示该步的完整命令与实测输出。点同一块可取消固定,按 Esc 也可取消。
rm 只是删掉目录项,只要进程还开着这个文件,内核就还留着它的 inode —— 于是能顺着 /proc/<PID>/fd 把它原样拷回来。
先确认实例是 OPEN,再把目标表空间的数据文件路径记下来(后面 6 步都用这个绝对路径)。
-- 实例状态:必须是 OPEN
SELECT INSTANCE_NAME, STATUS$, MODE$ FROM V$INSTANCE;
-- 目标表空间的数据文件绝对路径
SELECT FILE_ID, TABLESPACE_NAME, FILE_NAME
FROM DBA_DATA_FILES
WHERE TABLESPACE_NAME = '<表空间名>';
LINEID INSTANCE_NAME STATUS$ MODE$ 1 DM01 OPEN NORMAL LINEID FILE_ID TABLESPACE_NAME FILE_NAME 1 0 TEST_RM /home/dmdba/dmdata/DAMENG/TEST_RM01.DBF
看 dmserver 进程有没有打开这个数据文件。有 fd 指向它,才谈得上救;如果这里什么都没有,直接跳到「边界」章节走备份恢复。
# 取 dmserver 进程号
PID=$(pgrep -x dmserver)
echo $PID
# 看它打开了哪些 fd,过滤出目标数据文件
ls -l /proc/$PID/fd | grep '<数据文件名>'
$ ls -l /proc/2693/fd | grep TEST_RM lrwx------ 1 dmdba dinstall 64 Oct 26 21:10 44 -> /home/dmdba/dmdata/DAMENG/TEST_RM01.DBF记住这个 44,第 6 步要用。
文件已经没了,此刻最重要的不是查原因,而是别让句柄消失。
systemctl restart / shutdown / 重启机器都不行)。重启后进程退出,inode 随之释放,句柄法永久失效。正确动作只有三件:立刻拍照当前 fd 列表 → 通知业务方 → 按第 5、6、7 步执行。
误删之后数据库往往看起来正常,这是最容易误判的地方。手动触发一次文件系统校验,真相立刻出来。
-- 校验前:还能查(缓存 + 句柄还在,别当没事)
SELECT COUNT(*) FROM <用户>.<表>;
-- 触发数据文件存在性校验
CALL SP_FILE_SYS_CHECK();
-- 校验后再查:表空间已脱机
SELECT COUNT(*) FROM <用户>.<表>;
LINEID CNT_BEFORE_CHECK 1 1000 used time: 0.932(ms). Execute id is 64701. SQL> CALL SP_FILE_SYS_CHECK(); DMSQL executed successfully used time: 2.330(ms). Execute id is 64702. SQL> SELECT COUNT(*) AS CNT_AFTER_CHECK FROM TEST_RM.T2; [-3433]:tablespace is offline. used time: 0.348(ms). Execute id is 0.注意报错码:本版本(V8 2026-08-03)报的是
[-3433] tablespace is offline;老版本材料里常见的是 [-3430] 文件已被删除。看到哪个都别慌,处理路径相同。
告诉数据库:这个表空间我马上要把文件补回去,先进入恢复准备状态。
CALL SP_TABLESPACE_PREPARE_RECOVER('<表空间名>');
SQL> CALL SP_TABLESPACE_PREPARE_RECOVER('TEST_RM');
DMSQL executed successfully
used time: 0.529(ms). Execute id is 64801.
从第 2 步记下的 fd 号,把 (deleted) 的 inode 拷回原路径。这一步决定生死。
# 再取一次进程号(别复用旧变量,防止实例被动重启过)
PID=$(pgrep -x dmserver)
# 找出那个标着 (deleted) 的 fd 号
FD=$(ls -l /proc/$PID/fd | grep '<数据文件名>.*(deleted)' | awk '{print $9}')
ls -l /proc/$PID/fd/$FD
# 原样拷回原路径
cp /proc/$PID/fd/$FD <数据文件绝对路径>
echo "cp 退出码=$?"
# 属主必须与实例进程一致
chown dmdba:dinstall <数据文件绝对路径>
ls -l <数据文件绝对路径>
$ ls -l /proc/2693/fd/44 lrwx------ 1 dmdba dinstall 64 Oct 26 21:10 /proc/2693/fd/44 -> /home/dmdba/dmdata/DAMENG/TEST_RM01.DBF (deleted) $ cp /proc/2693/fd/44 /home/dmdba/dmdata/DAMENG/TEST_RM01.DBF cp 退出码=0 $ ls -l /home/dmdba/dmdata/DAMENG/TEST_RM01.DBF -rw-r--r-- 1 dmdba dinstall 134217728 Oct 26 21:11 /home/dmdba/dmdata/DAMENG/TEST_RM01.DBF校验点:拷回来的字节数必须与原文件一致(本例 134,217,728 = 128MB)。属主/属组不对,实例照样打不开。
文件回来了,还得让数据库认;最后必须用数据本身证明没丢行。
-- 执行恢复
CALL SP_TABLESPACE_RECOVER('<表空间名>');
CALL SP_FILE_SYS_CHECK();
-- 数据完整性:行数 + 主键区间
SELECT COUNT(*) AS CNT_RECOVERED FROM <用户>.<表>;
SELECT MIN(ID) AS MIN_ID, MAX(ID) AS MAX_ID FROM <用户>.<表>;
SELECT TABLESPACE_NAME, STATUS FROM DBA_TABLESPACES
WHERE TABLESPACE_NAME = '<表空间名>';
SQL> CALL SP_TABLESPACE_RECOVER('TEST_RM');
DMSQL executed successfully
used time: 8.769(ms). Execute id is 64901.
SQL> CALL SP_FILE_SYS_CHECK();
DMSQL executed successfully
used time: 44.185(ms). Execute id is 64902.
LINEID CNT_RECOVERED
1 1000
LINEID MIN_ID MAX_ID
1 1 1000
判据:行数回到 1000、主键区间 1–1000 连续,才算真的救回来。只看「执行成功」不算完。
/proc/<PID>/fd 里就找不到它。rm、mv 到其它分区、被清理脚本删掉都算删;如果用 dd 把原文件内容覆盖成 0,inode 还在但数据已经没了,句柄捞回来也是空壳。/proc/<PID>/fd 并能往数据目录写文件,通常就是 dmdba 或 root。误删之后立刻 SELECT 往往照样出结果,容易让人以为没事。原因有两层:一是数据页已经在内存缓存里;二是进程还握着文件句柄,底层读并没有断。
只有触发 SP_FILE_SYS_CHECK()(或某些操作主动做文件存在性校验)之后,数据库才会确认「这个路径上没文件了」,把表空间置为脱机。所以「还能查」不等于「没坏」。
rm 数据文件后,/proc/<PID>/fd 仍能看到 (deleted) 句柄;systemctl restart 重启实例,进程号从 2693 变为 2482,句柄命中数直接变成 0;[-510] System in mount status——不是单个表空间脱机,而是整个库打不开;SP_TABLESPACE_PREPARE_RECOVER / SP_TABLESPACE_RECOVER 都直接报 [-510],无法通过。不能。重启后 dmserver 进程退出,内核释放 inode,/proc/<PID>/fd 里的 (deleted) 条目随之消失(实测命中数 0)。此时实例通常起在 MOUNT 态,请直接转备份恢复流程。
看目标位置是否同一个文件系统。同分区内 mv 只是改名,文件根本没被删,句柄不会变成 (deleted),把文件挪回原位即可;跨分区 mv 等于「复制 + 删除」,原 inode 被释放,才能用本文的句柄法。
先别做 SP_TABLESPACE_RECOVER。对比原文件应有的字节数(本例 128MB=134,217,728),确认 cp 是在文件仍被打开期间执行的。若进程已经退出或文件已被关闭,拷到的内容可能不完整,此时不要强行恢复,转备份恢复更安全。
停业务的目的是避免误写扩大影响,但停业务过程中千万不要重启数据库。第 5–7 步都是在线执行的表空间级操作,影响面限于该表空间;是否停业务按现场风险决定,重启则绝对禁止。
让数据库重新校验文件是否存在、是否可用。只跑 SP_TABLESPACE_RECOVER 而不校验,可能拿到「恢复成功」但表空间仍不可用的假象;最后一定要用行数与主键区间证明数据完整。
把卡在第几步、报错原文、版本号发我就够判断了 —— 不用发 dm.ini 或任何配置文件。共性问题我会补进这篇,下一个人就不用再踩这个坑。
现象和上面几步都对不上?把已经试过的命令和结果说一下,我能判断是文档该补一句,还是版本或环境差异。真要贴参数,先把 IP、路径、口令替换掉。
想让答案别人也能查到?在 Gitee 仓库 提 issue,回复公开可查。