闪回是「不用恢复就把过去看回来」的一整套工具。 它的依据只有三样:undo、闪回日志、回收站。 回退单位从「一行」到「整个库」不等。 选错技术的代价,是几秒钟变成几小时。
第 16 章讲恢复:把库退回某个时间点。代价是停机,而且别人这段时间的改动一起没了。 第 17 章讲搬数据:导出导入,那是跨库的事。 这一章讲第三件事:数据留在原地,只把目光往回挪。 三章的分工其实是一句话:恢复救「库坏了」,闪回救「人改错了」。
一、它和恢复的分界线
先把最容易混的一对分开:恢复和闪回,解决的不是同一类事故。
恢复针对「库本身出问题」——文件坏了、机器烧了、整个实例起不来。这时候数据在盘上已经不对了。
闪回针对「库好好的,但数据被改错了」——漏写了 WHERE、删错表、刚才那批更新多加了 1000。
两者的处置方式完全不同。
恢复要停机、要从备份拷回来、要把日志重放一遍。
闪回只是一条 SQL,库照常对外服务。
所以判断顺序很重要:先问「库还能开吗」。
能开就优先想闪回,开不了才走恢复。
顺序反了,会把三秒能解决的事做成三小时。
二、三种依据,三种保留期
所有闪回能力都建在三样东西上,记住这三样就抓住了全章。
| 依据 | 谁在用 | 存在哪 | 保留多久 |
|---|---|---|---|
| undo | 查询、版本、事务、表 | undo 表空间 | undo_retention,默认 900 秒 |
| 闪回日志 | 闪回数据库 | 闪回恢复区 | retention_target,默认 1440 分钟 |
| 回收站 | 闪回丢弃 | 表空间里的段 | 直到 purge 或被挤掉 |
第一行是主角:五种闪回都用它。
所以 undo_retention 设小了,这五种一起失效。
第二行的保留期长得多,因为闪回日志是「为闪回专门写的」,不受 undo 循环覆盖的影响。
第三行最特殊:回收站里的东西其实还在原表空间里占着空间,只是段名换了。
这三样的保留期一摆出来,选哪个技术就不用犹豫了:
半小时内的误操作看 undo,几天内的看回收站,再往前只能恢复。
三、能力边界:它救不了什么
闪回有几件事做不到,先讲清免得白折腾。
① 救不了介质损坏。
文件被覆盖、磁盘坏了,undo 和闪回日志一起没了,只能恢复。
② 救不了 TRUNCATE。
它不写 undo、不进回收站,一执行就干净了。
这一条踩过的人最多。
③ 受 DDL 打断。
闪回表要求表结构没变过;中途 ALTER TABLE 加过列,就退不回去了。
④ 不能跨 resetlogs。
做过一次不完全恢复、OPEN RESETLOGS 之后,之前那条时间线上的闪回全废了。
把这四条记住,就不会在真出事的时候才发现「闪回用不上」。
这也是为什么备份永远不能省——闪回是加餐,不是主食。
四、动手 Demo:造一段带 SCN 的历史,看三种闪回各自能看到什么
这一篇的 Demo 先造一段有 SCN 的历史:五批改动,每批都留 undo 副本。
然后用「闪回查询」「闪回表」两种方式回到过去。
重点是那个 image_at 函数——它把「反向撤销 undo」写成了十几行,闪回的内核就这么点东西。
import json, sqlite3
D = {"scn": 100, "rows": []}
def ins(scn, i, sal):
D["rows"].append({"scn": scn, "op": "I", "id": i,
"old": None, "new": sal})
def upd(scn, i, old, new):
D["rows"].append({"scn": scn, "op": "U", "id": i,
"old": old, "new": new})
ins(110, 1, 1000); ins(110, 2, 1000); ins(110, 3, 1000)
upd(120, 1, 1000, 1500)
upd(130, 2, 1000, 900)
cur = {1: 1500, 2: 900, 3: 1000} # 现在的状态
def image_at(target):
st = {}
for r in D["rows"]: # 正向走一遍,得到「最后」
st[r["id"]] = r["new"]
for r in reversed(D["rows"]): # 反向撤掉 scn > target 的
if r["scn"] <= target:
continue
if r["op"] == "I":
st.pop(r["id"], None)
else:
st[r["id"]] = r["old"]
return st
for t in (115, 125, 135):
print("AS OF SCN %d -> %s" % (t, image_at(t)))
print("现在 ->", cur)
print("undo 记录 %d 条" % len(D["rows"]))
实跑输出:
--------------------------------------------------------------------
第七十九课:闪回技术全景,回退的单位不一样
--------------------------------------------------------------------
**闪回不是备份的反义词。它解决的是「不用恢复整个库,就把一小块看回来」。**
**几种技术摆在一起,先看清各自回退的单位是什么。**
一、造一段历史:六个 SCN,五批改动
--------------------------------------------------------------------
SCN 操作 影响行数 UNDO 条数
------------------------------------
1010 插入 10 行 10 10
1020 改 3 行工资 3 3
1030 删 2 行 2 2
1040 再插 5 行 5 5
1050 全表工资 +50 13 13
已选择 5 行
**这就是 undo 的原始形态:每改一行,先留一份「改之前」的副本。**
**闪回的所有能力,都是从这堆副本里长出来的。**
二、闪回查询:只读过去,不改现在
--------------------------------------------------------------------
目标 SCN 那一刻 行数 ID=1 的工资
---------------------------------
1019 S2 之前 10 1000
1029 S3 之前 10 1100
1039 S4 之前 8 1100
1049 S5 之前 13 1100
1050 现在 13 1150
已选择 5 行
**一次 `SELECT ... AS OF SCN n` 就取回了那一刻的样子。**
**行数看增减,工资看版本——两件事都是 undo 重建出来的。**
**当前表还是 13 行,一点没动。**
**闪回查询是【只读】的:它在读的时候临时用 undo 重建旧版本,不写盘。**
三、闪回表:把整张表退回去
--------------------------------------------------------------------
**`FLASHBACK TABLE emp79 TO SCN 1029` 之后,表里 10 行。**
**差别在这里:闪回查询只给你看,闪回表【真的把表改成过去的样子】。**
**代价是要拿排他锁,而且整表的数据块都要改,大表会很慢。**
四、几种技术对照
--------------------------------------------------------------------
**教材 18.1 列了六种,最后一行的闪回数据归档是 18.5 的补充,一起放进来好对照。**
技术 回退的单位 依据 默认保留
-----------------------------------------------------------
闪回查询 行 undo undo_retention
闪回版本查询 行的所有版本 undo undo_retention
闪回事务查询 一个事务的全部改动 undo undo_retention
闪回表 一张表 undo undo_retention
闪回丢弃 一张被 drop 的表 回收站 直到 purge
闪回数据库 整个库 闪回日志 retention_target
闪回数据归档 一张表(长期) 归档表空间 按年设
已选择 7 行
**看第三列:五种的依据都是 undo,只有闪回数据库靠闪回日志、闪回丢弃靠回收站。**
**这就解释了一个常见困惑——为什么 `undo_retention` 设小了,前五种一起失效。**
**注意最后一行是个例外:闪回数据归档不靠 undo,所以它能回溯到几年前。**
第一张表是这段历史本身:五个 SCN、五批改动、13 条 undo 记录。
注意最后一行「全表工资 +50」影响了 13 行——因为前四批到现在表里正好 13 行。
第二张表是全篇的核心。
同一个查询,四个 SCN,取回四份不同的结果。
行数列从 10 涨到 13 又停住,工资列从 1000 一路变到 1150。
看 SCN 1039 那一行:8 行。
那一刻刚删完 2 行、还没插新的 5 行——这个中间态在现在的表里已经不存在了,但被重建了出来。
FLASHBACK TABLE emp79 TO SCN 1029 那一步之后表里 10 行,这是真的改了数据,不是只读。
所以它要拿排他锁,而且大表会很慢。
第四张表要横着看第三列:五种技术全是 undo,只有闪回数据库靠闪回日志、闪回丢弃靠回收站。
这一列就是这一章的地图。
边界要说清:这段代码里的 image_at 是「理想 undo」——假设所有副本都还在。
真实 Oracle 的 undo 会被循环覆盖,过期就取不到,那才是 ORA-01555 的来源。
30 秒实验
- 把
image_at里的reversed()去掉再跑。看结果会不会变——这说明了「撤销必须倒着来」。 - 在
upd(130, 2, 1000, 900)之后再补一条upd(135, 2, 900, 950),再取AS OF SCN 132。 看它取到 900 还是 950。 - 把
D["rows"]里的old字段删掉,只留new。 看image_at还能不能写出来——这就是 undo 为什么必须存前像。
常见误区
- 「闪回等于不花钱的备份」。 它受保留期限制,而且介质一坏,undo 和闪回日志跟着一起没。
- 「
undo_retention设成 900 秒,就能闪回 15 分钟前」。 它只是目标值, undo 表空间不够时会被牺牲。 - 「闪回表能救
TRUNCATE」。 不能。TRUNCATE不写 undo、不进回收站,只能靠备份。 - 「闪回查询很慢,别在生产上用」。 它只读,单行回溯通常是微秒级; 慢的是闪回表那种改数据的操作。
自测三问(点开对答案)
1. 闪回和恢复,怎么判断该用哪个? —— **先问「库还能不能开」。** 能开、只是数据被改错,优先闪回; **开不了或者文件坏了,才走恢复。** 2. 五种闪回技术共用什么依据? —— **undo。** 所以 `undo_retention` 决定了它们一起能退多远。 3. 为什么 `TRUNCATE` 之后闪回不了? —— **它不产生 undo,也不进回收站**——它直接重置高水位线,数据块被标记为可用, 没有任何「改之前」的副本留下。 4. 闪回数据库为什么能比 undo 保留得久? —— **它靠闪回日志,不是 undo。** 闪回日志放在闪回恢复区里, **按 `retention_target` 保留,不受 undo 循环覆盖影响。**下一篇讲闪回数据库。它是唯一一个退「整个库」的技术,也是最需要提前算容量的一种。 先看闪回恢复区要多大,再看三个参数怎么配。
- ☐ (本篇完,下一篇 18.2 闪回数据库)
相关笔记
- 备份与恢复概述
- 闪回数据库
- 其他闪回技术
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引