DROP TABLE 在 Oracle 里默认不删数据,只把表挪进回收站。
所以 drop 之后还有一次后悔的机会。
回收站里的东西照样占表空间。
只 drop 不 purge,盘一点都不会松。
18.3 处理的是「行被改错了」。这一篇处理的是「整张表被删了」。
两者的依据完全不同:一个靠 undo,一个靠回收站。
所以闪回丢弃不受 undo_retention 限制——它能退多久,只取决于你有没有 purge。
一、回收站是什么
它不是一张表,也不是一个目录。
它是「被 drop 的对象在表空间里留下的那些段」。
DROP TABLE emp 做了三件事:
① 把段名从 EMP 改成 BIN$...;
② 在数据字典里记一条「它原来叫什么」;
③ 从你的 schema 里摘掉这个名字。
数据块一个都没动。
这就是为什么 drop 一张 100 GB 的表是秒级操作,而 delete 全表要跑几小时。
看回收站有两条路:
SHOW RECYCLEBIN;
SELECT object_name, original_name, type, droptime,
can_undrop, can_purge, space
FROM USER_RECYCLEBIN;
SPACE 那一列是它占的块数。
这个视图在 DBA 角度看叫 DBA_RECYCLEBIN,多一列 OWNER。
CAN_UNDROP 和 CAN_PURGE 两列很实用:
表空间做过字典管理的迁移、或者段被部分覆盖,前者会变成 NO,那时候就闪回不了了。
二、drop 之后发生了什么
最容易踩的一个细节:原名立刻被释放。
所以 drop 完马上可以建一张同名表,两张表不冲突。
这就带来一个坑:回收站里可以同时有好几个「emp」。
这时候 FLASHBACK TABLE emp TO BEFORE DROP 会报错——它不知道你要哪一个。
两种指定方式:
-- 按 BIN$ 名精确指定
FLASHBACK TABLE "BIN$abc123==$0" TO BEFORE DROP;
-- 或者闪回时改名
FLASHBACK TABLE emp TO BEFORE DROP RENAME TO emp_bak;
不写 RENAME TO 时,它默认恢复成 ORIGINAL_NAME。
所以如果原名已经被占了,还是会撞。
实践里推荐永远写 RENAME TO。
理由很实在:你要恢复的多半是「旧数据」,而原名上很可能已经有一张「新表」在跑业务。
先恢复成 _bak,比对完再决定谁留,比直接覆盖安全得多。
三、闪回表到 drop 之前
命令就是上面那条,但有几个细节值得单独说。
① 恢复的是「drop 那一刻」的样子,不是更早。
所以它救不了「上周误删了一批行」——那是 undo 的活儿。
② 索引、约束、触发器会一起回来。
因为它们也是被 drop 的独立对象,都在回收站里。
③ 依赖它的视图、存储过程不会回来。
那些对象在 drop 表的时候被标成 INVALID 了,表恢复之后要重新编译。
④ 外键约束要小心。
如果别的表上有指向它的外键,drop 的时候会被一起删掉,恢复表不会自动恢复外键。
第 ④ 条最容易出事。
表回来了、数据也回来了,你以为搞定了,
结果某个关联表插数据时不再校验——数据一致性从这里开始烂。
所以恢复之后必做一件事:查一遍约束在不在。
SELECT table_name, constraint_name, constraint_type, status
FROM USER_CONSTRAINTS
WHERE table_name = 'EMP';
四、purge:把后悔的机会交出去
PURGE 才是真删。它有四种用法,区别在「删哪个范围」。
| 写法 | 删什么 |
|---|---|
PURGE TABLE emp |
回收站里那个叫 emp 的 |
PURGE INDEX idx_name |
回收站里的索引 |
PURGE RECYCLEBIN |
当前用户回收站里的全部 |
PURGE TABLESPACE ts USER u |
某表空间里某用户的 |
还有一种「跳过回收站」的写法,在建表或删表时用:
DROP TABLE emp PURGE; -- 直接删,不进回收站
CREATE TABLE t (...) PURGE; -- 以后 drop 它都不进回收站
第二行那个 PURGE 在建表语句里,容易被忽略。
它的意思是「这张表以后被 drop 时,别进回收站」。
用在临时表、中间表上很合适——那些表本来就不需要后悔的机会。
注意 TRUNCATE 和 DROP ... PURGE 还不一样。
TRUNCATE 保留表结构、清空数据,连回收站都不进;
DROP ... PURGE 连表一起没了。
两者共同点是:都没有后悔药。
五、回收站也会撑爆表空间
这是最容易被忽略的运维问题。
一个系统每天 drop 一批中间表,跑了半年。
表空间一路涨,DBA 一直加数据文件,加到没地方加。
最后发现:涨的全是回收站。
怎么防?三条:
① 临时表、中间表建的时候带 PURGE。
② 定期 PURGE RECYCLEBIN,或者至少监控它的总量。
③ 给回收站设个上限——不过这个只能靠表空间配额间接控制,
Oracle 没有「回收站最多占多少」这个参数。
监控语句:
SELECT SUM(space) * 8192 / 1024 / 1024 AS mb
FROM USER_RECYCLEBIN;
还有一点要说:表空间真的紧张时,Oracle 会自己清回收站。 顺序是先清最老的。 所以「回收站里的东西一定还在」这个假设不成立——压力大了它自己就没了,而且不通知你。 结论很直接:把回收站当「顺手留的一道保险」,别当「正式的备份手段」。
六、动手 Demo:用真实文件把「挪进回收站」和「挪回来」跑一遍
这一篇的 Demo 全是真的文件操作:shutil.move、os.remove、目录大小统计。
因为「drop 不删数据」这件事,本质就是文件挪了个位置——用文件系统演一遍,比任何比喻都直观。
import os, shutil, tempfile, sqlite3
T = tempfile.gettempdir()
R = os.path.join(T, "recycle_demo")
if os.path.exists(R):
shutil.rmtree(R)
os.makedirs(os.path.join(R, "RECYCLEBIN"))
p = os.path.join(R, "emp.db")
c = sqlite3.connect(p)
c.execute("create table t (id int primary key, v text)")
c.executemany("insert into t values (?,?)",
[(i, "v%05d" % i) for i in range(1, 5001)])
c.commit()
c.close()
print("建表后 %.2f MB" % (os.path.getsize(p) / 1048576.0))
# DROP TABLE:文件挪进回收站,数据没删
bn = os.path.join(R, "RECYCLEBIN", "BIN$0001==$0.db")
shutil.move(p, bn)
print("DROP 之后原文件在吗: %s,回收站里 %d 个文件"
% (os.path.exists(p), len(os.listdir(os.path.join(R, "RECYCLEBIN")))))
# FLASHBACK TABLE ... TO BEFORE DROP:挪回来
shutil.move(bn, p)
c = sqlite3.connect(p)
print("闪回之后读到 %d 行"
% c.execute("select count(*) from t").fetchone()[0])
c.close()
print("盘上还是 %.2f MB,drop 不释放空间"
% (os.path.getsize(p) / 1048576.0))
实跑输出:
--------------------------------------------------------------------
第八十二课:闪回丢弃——drop 之后还有一次后悔的机会
--------------------------------------------------------------------
**`DROP TABLE` 在 Oracle 里默认【不删数据】,只是把表挪进回收站。**
**这一课用真实文件把「挪进去」和「挪回来」跑一遍,顺便量占多少盘。**
一、建三张表,量原始大小
--------------------------------------------------------------------
表名 行数 文件大小
----------------------
emp82 20000 0.66 MB
dept82 5000 0.18 MB
bonus82 3000 0.11 MB
已选择 3 行
**三个文件合计 0.95 MB。**
二、DROP TABLE:数据挪进回收站
--------------------------------------------------------------------
回收站里的名字 原名 DROP 时 SCN 大小
---------------------------------------------
BIN$000BC2==$0.db emp82 3010 0.66 MB
BIN$000BCC==$0.db dept82 3020 0.18 MB
BIN$000BD6==$0.db bonus82 3030 0.11 MB
已选择 3 行
**三张表都不见了,但盘上那份数据还在:回收站占 0.95 MB。**
**名字变成 `BIN$...` 是重点**——原名被释放了,你可以立刻建一张同名表。
**在 SQL*Plus 里查 `USER_RECYCLEBIN`(或 `SHOW RECYCLEBIN`)就能看到这四列。**
三、重名:回收站里可以有多个同名对象
--------------------------------------------------------------------
**现在回收站里叫 `emp82` 的有 2 个。**
**这时候直接 `FLASHBACK TABLE emp82 TO BEFORE DROP` 会报错**,因为它不知道你要哪一个。
**要么用 `BIN$` 名,要么加 `RENAME TO emp82_old`。**
四、FLASHBACK TABLE ... TO BEFORE DROP:挪回来
--------------------------------------------------------------------
**闪回 SCN 3010 那一份:挪回原名,读到 20000 行。**
**注意恢复的是【DROP 那一刻】的样子,不是更早。**
**所以回收站救不了「上周误删了一批行」——那是 undo 的活儿。**
五、PURGE:真的删掉
--------------------------------------------------------------------
**上一步闪回拿走了 0.66 MB,所以现在回收站只剩 0.32 MB。**
**`PURGE RECYCLEBIN` 之后:从 0.32 MB 降到 0.00 MB。**
**放掉 0.32 MB。这些数据再没有任何办法找回来,只能靠备份。**
操作 数据在哪 还能闪回吗
------------------------------------------------
DROP TABLE 回收站 能
DROP TABLE ... PURGE 没了 不能
PURGE RECYCLEBIN 没了 不能
TRUNCATE TABLE 没了 不能(不进回收站)
已选择 4 行
**最后一行最容易吃亏:`TRUNCATE` 不写 undo、不进回收站,它和 `DELETE` 完全是两件事。**
**空间账也要说清:drop 之后空间【不释放】。要释放得 purge,或者等表空间压力大时被自动挤掉。**
第一张表:三张表 20000/5000/3000 行,文件 0.66/0.18/0.11 MB,合计 0.95 MB。
这个数是后面所有对比的基线。
第二张表是 drop 之后的样子。
注意四列:名字变成 BIN$000BC2==$0.db 这种,原名还在,SCN 记着是什么时候 drop 的,
大小一点没变。
「大小一点没变」这句是重点。
drop 之后盘上那份数据完好无损,只是名字换了、并且从你的 schema 里摘掉了。
第四段是闪回:从 BIN$000BC2==$0.db 挪回 emp82.db,读到 20000 行。
注意恢复的是 SCN 3010 那一份,也就是【drop 那一刻】的样子。
第五段那个对比最能说明问题:purge 之前回收站 0.32 MB,purge 之后 0.00 MB。
放掉的这 0.32 MB 再没有任何办法找回来。
最后那张四行表要背下来。
只有第一行「DROP TABLE」还能闪回,其余三种(包括 TRUNCATE)都是真没了。
还有一个数字容易被忽略:闪回之后「盘上还是 0.66 MB」。
说明整个过程磁盘占用从来没变过——drop 不释放空间,闪回也不额外占空间。
边界要说清:真实 Oracle 的回收站是「表空间里的段」,不是文件。
这里用文件模拟,能演对「挪位置」这层语义,但演不出「段被新数据覆盖」这种情况。
30 秒实验
- 把回收站目录改成两层(按日期分目录),再跑一次。 体会一下 Oracle 为什么不需要这么做——它靠数据字典记元信息,不靠目录结构。
- 在
shutil.move(p, bn)之后再加一次shutil.move,把第二个同名表也挪进去。 看闪回时怎么区分两个BIN$名。 - 把
os.remove(bn)换成「先写满一个新文件再删」, 看文件系统层面「删除」到底做了什么——这也是 OraclePURGE和「数据被覆盖」的差别。
常见误区
- 「
DROP TABLE会把数据删掉」。 默认不删,只改段名、记字典,数据块一个不动。 - 「drop 之后空间就释放了」。 不释放,要
PURGE或者等表空间紧张时被自动挤掉。 - 「回收站能找回上周误删的行」。 不能。它只保存 drop 那一刻的整表快照,行级的要靠 undo。
- 「恢复表之后一切照旧」。 外键约束不会自动恢复,视图和存储过程要重新编译。
自测三问(点开对答案)
1. `DROP TABLE` 到底删了什么? —— **只删了「名字」和「字典里的登记」。** 段名改成 `BIN$...`, **数据块原封不动,这就是它能秒级完成的原因。** 2. 回收站里有两个同名表,怎么闪回指定那一个? —— **用 `BIN$` 名精确指定**,或者**`FLASHBACK TABLE emp TO BEFORE DROP RENAME TO emp_bak`。** 3. 为什么建议闪回时总带 `RENAME TO`? —— **因为原名多半已经被新表占了。** 先恢复成 `_bak`、**比对完再决定谁留,比直接覆盖安全。** 4. 只 drop 不 purge,表空间会怎样? —— **一直涨,一点不松。** 而且表空间真紧张时, **Oracle 会自己从最老的开始清回收站,还不通知你。**下一篇收尾:闪回版本查询、闪回事务查询、闪回数据归档。 前两个是「查清是谁改的、怎么改回去」,第三个是「把历史存到 undo 管不到的地方」。
- ☐ (本篇完,下一篇 18.5 其他闪回技术)
相关笔记
- 闪回表
- 其他闪回技术
- 闪回技术概述
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引