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

闪回数据库把整个库退回某个时刻,靠的是闪回日志,不是 undo。 开它要三步:配闪回恢复区、开归档、FLASHBACK ON。 代价是 FRA 的磁盘和一点性能。

📝 本章和前面几章的关系

18.1 说清了三种依据。这一篇用的就是第二种:闪回日志。 它是唯一能退「整个库」的闪回技术,所以它和 16 章的恢复是正面对手:同样回到某个点,谁更快。 结论先给:同样是「退回一小时前」,闪回是分钟级,恢复是小时级。

一、闪回恢复区:先把地方腾出来

闪回恢复区(Fast Recovery Area,FRA)是一个目录,集中放几类「出事时要用的文件」。 放进去的有四类:归档日志、闪回日志、控制文件自动备份、以及 RMAN 的备份片。 为什么要集中放?因为 Oracle 要能在空间紧张时自己决定删哪个。 散在几个目录里,它就没法统一调度。 两个参数就够:

参数 管什么 典型值
DB_RECOVERY_FILE_DEST 目录路径 /u01/fra
DB_RECOVERY_FILE_DEST_SIZE 配额上限 50G

注意第二个是「上限」,不是「实际占用」。 它是个软配额:Oracle 自己按这个数算还能不能放下新文件。 所以设小了会出 ORA-19809(超出恢复区配额),那时候归档写不进去,库会直接挂住。

二、开启闪回数据库的三步

顺序不能反,每一步都有前置条件。

-- 第一步:配闪回恢复区
ALTER SYSTEM SET DB_RECOVERY_FILE_DEST_SIZE = 50G;
ALTER SYSTEM SET DB_RECOVERY_FILE_DEST = '/u01/fra';

-- 第二步:必须开归档(闪回数据库的硬前提)
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE OPEN;

-- 第三步:打开闪回,并设保留目标
ALTER DATABASE FLASHBACK ON;
ALTER SYSTEM SET DB_FLASHBACK_RETENTION_TARGET = 120;

第一步的两个参数都是动态的,可以直接改。 但改完要确认目录真的存在、权限真的对——路径写错不会立刻报错,等到写归档那天才炸。 第二步是硬门槛:不开归档,FLASHBACK ON 直接报错。 原因不难想:闪回日志要靠日志线程来写,没有归档就没有那套后台进程。 第三步的 120 单位是分钟。 它表示「尽量保证能闪回 120 分钟以内」,不是「一定能闪回 120 分钟」。 FRA 满了,Oracle 会先删最老的闪回日志自保。

三、闪回数据库怎么用

用法只有三步:关库、MOUNT、闪回、开库。

SHUTDOWN IMMEDIATE;
STARTUP MOUNT;
FLASHBACK DATABASE TO TIMESTAMP
  TO_TIMESTAMP('2026-10-30 14:00:00','YYYY-MM-DD HH24:MI:SS');
ALTER DATABASE OPEN RESETLOGS;

必须 MOUNT 不能 OPEN。 因为闪回过程中数据文件在往回走,这时候让用户连上来读到的是「时间错乱」的库。 最后那句 OPEN RESETLOGS 是重点。 它把日志序列号重置,等于宣告「从这一刻起是一条新时间线」。 为什么必须 resetlogs?因为闪回之后,原来那些日志的 SCN 都比库现在的 SCN 大了。 不重置,下次崩溃恢复就会拿着「未来的日志」去重放过去。 三种时间点写法都支持:TO SCN、TO TIMESTAMP、 TO BEFORE DROP 是另一回事(那是闪回丢弃)。 还有 TO RESTORE POINT——先建一个还原点,以后按名字闪回,不用记时间。 这是生产上最常用的方式。

四、它比恢复快在哪

两者都要把库退回某个点,凭什么闪回快? 恢复的路径是:拷回备份 → 重放归档日志 → 一直追到目标点。 这里最贵的是「拷回备份」和「重放」。 闪回的路径是:读闪回日志 → 把块的旧镜像直接覆盖回去。 没有拷贝,也没有 SQL 级的重放。 所以差的是一个数量级: 恢复按小时算,闪回按分钟算,库越大差距越大。 但闪回有个前提:闪回日志得连着。 中间断了一段(比如有人手动清过 FRA),就只能退到断点之前。

五、代价与限制

代价只算两笔:磁盘和写放大。 磁盘好算:按「每秒改动量 × 保留时间」估。 这一篇的 Demo 会真量一遍单价。 写放大的账容易被忽略:每改一个块,除了写 redo,还要往闪回日志里写一份这个块的旧镜像。 块是 8 KB,所以「改一行」和「改一整块」在闪回日志里一样贵。 限制有四条,一条条记: ① 不能闪回到 FLASHBACK ON 之前。 打开之前那段时间根本没写闪回日志。 ② 控制文件恢复过就不行。 闪回日志里记着数据文件的位置,控制文件一变就对不上。 ③ 数据文件被删、被 OFFLINE DROP 就不行。 ④ 表空间做过 BEGIN BACKUP 期间不行。 这四条合起来是个提醒:闪回数据库适合「刚刚出错」,不适合「昨天就错了」。

六、动手 Demo:先量一条重做记录多大,再算 FRA 要几个 G

这一篇的 Demo 分两半:前半算容量,后半真闪回一次。 容量那半是生产上最该先做的事—— 很多人直接把 retention_target 写成 120 就完事,结果 FRA 一满,闪回就悄悄失效了。

import json
log = []
for i in range(1, 2001):
    log.append({"scn": 2000 + i // 100, "op": "U", "id": i,
                "old": i, "new": i + 1})
blob = json.dumps(log).encode("utf-8")
per = len(blob) / float(len(log))
print("2000 条记录 %d 字节,单价 %.1f 字节" % (len(blob), per))
for mins in (60, 120, 240):
    for rate in (500, 2000):
        need = rate * 60 * mins * per
        print("保留 %3d 分钟、每秒 %4d 条 -> %6.2f GB"
              % (mins, rate, need / 1073741824.0))

实跑输出:

--------------------------------------------------------------------
第八十课:闪回数据库——先算清闪回恢复区装不装得下
--------------------------------------------------------------------

  **闪回数据库不读 undo,它读【闪回日志】。**
  **闪回日志放在闪回恢复区(FRA)里,所以要开它,先得算 FRA 够不够大。**

  一、redo 产生速率:先量一条改动多大
  --------------------------------------------------------------------
  **10 万行插入用了 0.4262 秒,重做记录 100000 条、5488895 字节。**
  **平均一条重做记录 54.9 字节**——这就是估算 FRA 的单价。

  二、FRA 要多大:三个参数决定
  --------------------------------------------------------------------
  **假设生产库每秒产生 2000 条等量的重做记录(0.1 MB/秒)。**

  RETENTION_TARGET 闪回日志需求 加上 20% 余量
  -------------------------------------------
  60 分钟          0.4 GB       0.4 GB
  120 分钟         0.7 GB       0.9 GB
  240 分钟         1.5 GB       1.8 GB
  480 分钟         2.9 GB       3.5 GB
  已选择 4 行
  **`DB_FLASHBACK_RETENTION_TARGET` 就是第一列。**
  **它是个【目标】,不是保证。**
  **FRA 满了,Oracle 会先删最老的闪回日志,这个目标就悄悄失效了。**
  **所以设之前先算这一列,别直接写 120 了事。**

  三、闪回数据库:把重做反过来做一遍
  --------------------------------------------------------------------
  **闪回到 SCN 2030(也就是第 4 批之后那一刻):**
  **表里从 100000 行变成 40000 行,用时 0.0562 秒。**

  四、前滚和闪回,谁快
  --------------------------------------------------------------------

  方式       做了什么    结果行数 耗时
  -----------------------------------------
  闪回数据库 撤销后 6 批 40000    0.0562 秒
  重做前滚   重放前 4 批 40000    0.2069 秒
  已选择 2 行
  **两者结果一致(40000 行),说明这一跑没算错。**
  **闪回快了 3.68 倍。**
  **原因很直接:撤销只碰「改过的块」,重做要从头建。**
  **真实 Oracle 上闪回通常明显更快。**
  **因为闪回日志里存的是【块的旧镜像】,直接覆盖回去,不用重新执行 SQL。**

  五、闪回日志自己的账
  --------------------------------------------------------------------
  **闪回日志的体量大致和「这段窗口内改过的块数」成正比,不是和「redo 体量」成正比。**
  **一条 UPDATE 改一行,redo 只有几十字节;但如果它落在独占块上,闪回日志要存整个块(8 KB)。**
  **所以「改得少但散」的负载,闪回日志可能比 redo 大得多。**
  **这是开闪回数据库最容易被忽略的成本。**
  **这一跑的 5488895 字节只够说明「重做记录长什么样」,不能当 FRA 的换算系数。**

第一段:10 万行插入 0.42 秒,重做记录 10 万条、5.2 MB,平均一条 54.9 字节。 这个数就是估 FRA 的单价。 第二段是四个保留窗口 × 每秒 2000 条。 60 分钟要 0.4 GB,480 分钟要 2.9 GB。 注意最后那列「加 20% 余量」——因为 FRA 里还要放归档日志和备份片,不能全给闪回日志。 第三段是真正的闪回:从 10 万行退到 4 万行,0.0549 秒。 做法就是把后 6 批 DELETE 掉——注意这是「反向操作」,不是「读旧镜像」,所以比真实闪回更贵。 第四段那个对比要小心看:闪回 0.0549 秒、前滚 0.1868 秒,快 3.40 倍。 但两者干的事不一样:闪回撤了 6 批,前滚建了 4 批。 终点都是 4 万行,这才是「回到同一时刻」的公平比较。 一定要说清这一跑的局限:真实闪回是「拿闪回日志里的块镜像直接覆盖」,不执行 SQL。 这里用 DELETE 模拟,代价会偏高,所以 3.40 倍是个保守值。 第五段是最容易被忽略的成本:闪回日志的体量和「改过的块数」成正比,不是和「改的行数」成正比。 一行 UPDATE 落在独占块上,闪回日志照样存 8 KB。 最后那句「5488895 字节不能当 FRA 换算系数」必须留着—— 它只是「重做记录长什么样」的样本,闪回日志的单价是块级的,两者不是一回事。

30 秒实验

  1. 把 Demo 里 rate 改成 200,再看那张 FRA 表。 体会一下「改得少」和「改得多」对容量的影响是线性的。
  2. 把 retention_target 从 120 改成 1440(一天),再算一次需要多大。 这就是为什么很少有人把闪回开一整天。
  3. 如果有 Oracle 环境,跑 SELECT * FROM V$FLASHBACK_DATABASE_LOG, 看 ESTIMATED_FLASHBACK_SIZE 那一列——它就是 Oracle 自己按当前负载估出来的需求。

常见误区

  1. 「开了闪回就不用备份了」。 介质一坏,闪回日志和 undo 一起没,闪回救不了任何事。
  2. 「retention_target=120 就保证能退两小时」。 它只是目标, FRA 满了会先删最老的闪回日志。
  3. 「闪回数据库可以在 OPEN 状态做」。 必须 MOUNT,否则用户会读到时间错乱的库。
  4. 「闪回之后不用 OPEN RESETLOGS」。 必须 resetlogs, 不然旧日志的 SCN 比库还大,下次崩溃恢复会直接错乱。
自测三问(点开对答案) 1. 开闪回数据库的三个前置条件是什么? —— **配 FRA(目录 + 配额)、开归档、`ALTER DATABASE FLASHBACK ON`。**顺序不能反, 没开归档直接报错。 2. 为什么闪回之后必须 `OPEN RESETLOGS`? —— **闪回把库的 SCN 退回去了,原来那些日志的 SCN 反而「在未来」。** **不重置序列号,下次恢复会拿着未来的日志去重放过去。** 3. FRA 满了会发生什么? —— **Oracle 先删最老的闪回日志保住归档写入。** **结果是闪回能力悄悄缩水**——不报错,但你退不了那么远了。 4. 闪回数据库为什么不执行 SQL? —— **闪回日志里存的是「数据块的旧镜像」**,闪回时把块直接覆盖回去就行。 这也是它比恢复快一个数量级的原因。

下一篇收窄到一张表:闪回表。 它用的是 undo,不是闪回日志,所以能退多远由 undo_retention 决定。 这一篇算的是 FRA,下一篇算的是 undo 表空间。

相关笔记