🏺 第18章 Oracle 的闪回技术
📄 一句话概括

闪回是「不用恢复就把过去看回来」的一整套工具。 它的依据只有三样: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 秒实验

  1. 把 image_at 里的 reversed() 去掉再跑。看结果会不会变——这说明了「撤销必须倒着来」。
  2. 在 upd(130, 2, 1000, 900) 之后再补一条 upd(135, 2, 900, 950),再取 AS OF SCN 132。 看它取到 900 还是 950。
  3. 把 D["rows"] 里的 old 字段删掉,只留 new。 看 image_at 还能不能写出来——这就是 undo 为什么必须存前像。

常见误区

  1. 「闪回等于不花钱的备份」。 它受保留期限制,而且介质一坏,undo 和闪回日志跟着一起没。
  2. 「undo_retention 设成 900 秒,就能闪回 15 分钟前」。 它只是目标值, undo 表空间不够时会被牺牲。
  3. 「闪回表能救 TRUNCATE」。 不能。TRUNCATE 不写 undo、不进回收站,只能靠备份。
  4. 「闪回查询很慢,别在生产上用」。 它只读,单行回溯通常是微秒级; 慢的是闪回表那种改数据的操作。
自测三问(点开对答案) 1. 闪回和恢复,怎么判断该用哪个? —— **先问「库还能不能开」。** 能开、只是数据被改错,优先闪回; **开不了或者文件坏了,才走恢复。** 2. 五种闪回技术共用什么依据? —— **undo。** 所以 `undo_retention` 决定了它们一起能退多远。 3. 为什么 `TRUNCATE` 之后闪回不了? —— **它不产生 undo,也不进回收站**——它直接重置高水位线,数据块被标记为可用, 没有任何「改之前」的副本留下。 4. 闪回数据库为什么能比 undo 保留得久? —— **它靠闪回日志,不是 undo。** 闪回日志放在闪回恢复区里, **按 `retention_target` 保留,不受 undo 循环覆盖影响。**

下一篇讲闪回数据库。它是唯一一个退「整个库」的技术,也是最需要提前算容量的一种。 先看闪回恢复区要多大,再看三个参数怎么配。

相关笔记