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

闪回表把一张表退回某个时刻,用的是 undo,不是闪回日志。 要加 ENABLE ROW MOVEMENT,否则一行都退不动。 它能退多远,完全由 undo_retention 决定。 不是「设了就一定行」,是「undo 没被覆盖才行」。

📝 本章和前面几章的关系

18.2 退的是整个库,要停机。这一篇退一张表,库照跑。 所以两者不是大小之分,是「能不能不停机」之分。 它和 16 章恢复的关系要理清: 恢复会把别人的改动一起抹掉,闪回表不会。 这是它在生产上最值钱的地方。

一、命令的语法:三种时间点写法

核心就一条命令,变的是「回到哪个时刻」。

-- 按 SCN
FLASHBACK TABLE scott.emp TO SCN 1234567;

-- 按时间戳
FLASHBACK TABLE scott.emp TO TIMESTAMP
  TO_TIMESTAMP('2026-10-30 14:00:00','YYYY-MM-DD HH24:MI:SS');

-- 按还原点
FLASHBACK TABLE scott.emp TO RESTORE POINT before_fix;

三种写法等价,选哪种看手头有什么。 只知道「大概半小时前」就用 TIMESTAMP;有精确 SCN 就用 SCN;提前建了还原点就用名字。 最常用的其实是还原点。 因为「半小时前」这种说法,等你查清问题往往已经过了半小时。 还原点分两种:普通还原点会随 undo 一起过期, GUARANTEE FLASHBACK DATABASE 那种会钉住 undo 不许覆盖。 后者安全但有代价:undo 表空间会被撑大。

二、前置条件:四个都得满足

命令很短,条件不少。少一条就报错,而且报错信息不太好懂。

条件 为什么 不满足时
有 FLASHBACK 权限 它是 DDL 级操作 ORA-01031
ENABLE ROW MOVEMENT 闪回会改 rowid ORA-08189
表结构没变过 undo 只记数据不记结构 ORA-01466
undo 没被覆盖 依据就是 undo ORA-01555

第二行是最容易漏的。 ROW MOVEMENT 平时是关的,因为开着会让 rowid 变动,依赖 rowid 的应用会出问题。 但闪回必须开:它要把行放回原来的位置,位置被占了就得挪走,rowid 自然变了。 开启的方式:

ALTER TABLE scott.emp ENABLE ROW MOVEMENT;

第三行说的是「中途不能 ALTER TABLE」。 比如你删错了行,然后又给表加了一列,这时候闪回就退不回去了——因为 undo 里没有那一列的信息。 实践建议:发现删错,先别动表结构。 先把数据捞回来,再改结构。顺序反了就没救。

三、一次误更新的完整处置

场景很典型:晚上八点,有人执行了一条没写 WHERE 的 UPDATE。 处置顺序是固定的四步,照走就行。 ① 先确认范围。 用闪回查询看一眼改之前有多少行、改之后有多少行。 这一步不动数据,最安全。

SELECT COUNT(*) FROM scott.emp AS OF TIMESTAMP
  TO_TIMESTAMP('2026-10-30 19:55:00','YYYY-MM-DD HH24:MI:SS');

SELECT COUNT(*) FROM scott.emp;

② 再决定用哪种手段。 整张表都被改了,就用闪回表;只错了几行,直接照 undo SQL 改回去更快。 ③ 闪回之前先锁表。 不然闪回过程中别人又改了一遍,你退回去的还是错的。

LOCK TABLE scott.emp IN EXCLUSIVE MODE;
FLASHBACK TABLE scott.emp TO TIMESTAMP
  TO_TIMESTAMP('2026-10-30 19:55:00','YYYY-MM-DD HH24:MI:SS');

④ 核对结果。 别急着提交业务,先 SELECT 一遍关键行,确认工资、金额这些数对得上。 这四步里第三步最容易漏。 闪回表本身会加锁,但从「你决定闪回」到「锁住」之间有个窗口, 高并发表上这个窗口足够别人再改一轮。

四、undo 保留时间才是天花板

所有关于「能退多远」的问题,答案都在 undo_retention 上。 但这里有个反直觉的点:设了 900 秒,不等于一定能退 900 秒。 undo 表空间是个循环使用的空间。 新事务要空间、旧的又没到期,Oracle 会怎么选? 它优先保事务,牺牲保留时间——这是 undo_retention 的默认行为。 想让它真的守住,得开 RETENTION GUARANTEE:

ALTER TABLESPACE undotbs1 RETENTION GUARANTEE;

代价写清楚:开了之后,undo 表空间满了,事务会直接失败。 也就是「宁可业务报错,也不让 undo 被覆盖」。 这是个明确的取舍,别默认打开。 大多数系统的正确做法是:把 undo 表空间按速率算足(见 Demo),保留默认的 NOGUARANTEE。 最后一句提醒:闪回表会消耗 undo 吗?会。 它自己也是一次 DML,也要写 undo。 所以闪回一张大表,会把 undo 表空间再撑一轮。

五、动手 Demo:量一次误更新的 undo 体量,再故意让它不够

这一篇的 Demo 做三件事:量 undo 的大小、按速率倒推表空间、以及故意让保留时间不够, 看 ORA-01555 是怎么来的。 第三件事是重点。 那个错在生产上难以复现,这里用一个环形缓冲把它变得可复现。

import json
rows = [{"id": i, "amt": i % 500} for i in range(1, 50001)]
ub = len(json.dumps(rows).encode("utf-8"))
print("改 5 万行,undo 副本 %.2f MB" % (ub / 1048576.0))
for mins in (60, 900, 3600):
    print("保留 %4d 秒 -> %6.1f MB"
          % (mins, ub / 60.0 * mins / 1048576.0))

keep, ring = 100, []
for i in range(1, 501):
    ring.append(i)
    if len(ring) > keep:
        ring.pop(0)
print("缓冲里最老的一条是第 %d 次改动之前" % ring[0])
try:
    assert ring[0] <= 1
except AssertionError:
    print("回溯到第 1 次改动之前:ORA-01555 snapshot too old")

实跑输出:

--------------------------------------------------------------------
第八十一课:闪回表——undo 撑得住,才退得回去
--------------------------------------------------------------------

  **闪回表用的就是 undo。所以它能退多远,完全由 undo 的保留时间决定。**
  **这一课先把 undo 的体量量出来,再故意让它不够,看会报什么。**

  一、一次误更新,undo 有多大
  --------------------------------------------------------------------
  **20 万行的表,改其中 5 万行:undo 副本 50000 条、3.0 MB。**
  **更新本身用了 0.0653 秒。**
  **5 万行才 3.0 MB,看着不大**——这就是很多人敢把`undo_retention` 设成 900 的原因。

  二、按速率倒推 undo 表空间
  --------------------------------------------------------------------

  改动速率    UNDO_RETENTION=900 =3600    =10800
  ------------------------------------------------
  10 MB/分钟  0.15 GB            0.59 GB  1.76 GB
  50 MB/分钟  0.73 GB            2.93 GB  8.79 GB
  200 MB/分钟 2.93 GB            11.72 GB 35.16 GB
  已选择 3 行
  **这张表的口径是「保留窗口内所有 undo 都得留着」,所以直接拿速率乘窗口就行。**
  **本次实测只能提供单价:2.96 MB 对应 5 万行改动**,折成第一列要看你的库每分钟改多少行。
  **注意 `undo_retention` 只是【目标】,不是保证。**
  **undo 表空间不够用时,Oracle 会优先保事务、牺牲保留时间。**

  三、保留时间不够会怎样:ORA-01555
  --------------------------------------------------------------------
  **造一个只能存 1000 条 undo 的环形缓冲,然后连做 5000 次改动。**
  **现在缓冲里最老的一条是第 4001 条改动之前的镜像。**
  **想回溯到第 1 条改动之前:取不到了**
  **报的就是 `ORA-01555: snapshot too old`。**
  **它的字面意思是「快照太旧」,实际含义是「你要的那个版本已经被覆盖了」。**
  **这个错有个特点:不是每次都报,取决于别人改得多快。**
  **所以它难以复现,也容易在夜里批量任务上炸。**
  **对策只有两条:把保留时间拉长,或者把长查询拆短。**

  四、闪回表 vs 从备份重建
  --------------------------------------------------------------------

  方式         操作量      耗时
  ----------------------------------
  闪回表       改回 5 万行 0.0582 秒
  复制备份文件 整库 7.9 MB 0.0076 秒
  已选择 2 行
  **这里闪回没更快,因为它也要把那 5 万行逐行改回去。**
  **它的真正优势不是快,是【不停机、不丢别人的改动】。**
  **从备份恢复会把整个库退回备份点,别人的活儿也一起没了。**
  **闪回表只动你要动的那张表,别的事务照跑。这才是它值钱的地方。**

第一段:20 万行的表改 5 万行,undo 副本 5 万条、3.0 MB。 更新本身 0.0526 秒。 3 MB 这个数看着很小——这就是很多人敢把 undo_retention 设成 900 的原因。 但要看清前提:只改了 5 万行,而且每行只有四个短字段。 第二张表是按速率倒推。 第一列是「你的库每分钟改多少 MB」,后三列是对应保留窗口需要的表空间。 200 MB/分钟 + 保留 3 小时,就要 35 GB。 这张表的口径要说明:它假设「保留窗口内所有 undo 都得留着」。 现实里 Oracle 会尽量留,但保不住就覆盖——所以它是上限估计。 第三段是全篇重点:只能存 1000 条 undo 的环形缓冲,连做 5000 次改动。 现在缓冲里最老的那条是第 4001 次改动之前的。 想回到第 1 次之前,取不到,报 ORA-01555。 这个错的名字起得很误导——「快照太旧」,真正说的是「你要的那个版本已经没了」。 它真正的含义是「你要的那个版本已经被新事务覆盖了」。 所以它不是 bug,是 undo 空间不够的必然结果。 第四张表要看清一个反直觉的结果:闪回表 0.0684 秒,复制备份文件 0.0082 秒。 复制一个 7.9 MB 的文件,比逐行改回 5 万行快得多。 这不是说闪回表没用。 闪回表的价值不在快,在「不停机、不丢别人的改动」。 从备份恢复要把整个库退回备份点,别人的活儿一起没了。 这一条才是它值钱的地方。

30 秒实验

  1. 把 Demo 里 t81 的行数从 20 万改成 2 万,再跑一次。 看 undo 体量和更新耗时各变了多少—— 体会一下「undo 体量跟改的行数成正比、耗时跟数据量也有关」。
  2. 把 keep 从 1000 改成 100,再跑。看「最老的一条是第几次改动之前」怎么变。
  3. 如果有 Oracle 环境,跑 SELECT * FROM V$UNDOSTAT。 MAXQUERYLEN 那一列就是当前最长查询已经跑了多少秒——undo_retention 至少要大于它。

常见误区

  1. 「闪回表不需要任何前置条件」。 要 ENABLE ROW MOVEMENT,否则直接 ORA-08189。
  2. 「undo_retention=900 就一定能退 15 分钟」。 它是目标值,undo 不够时会被牺牲; 要真守住得开 RETENTION GUARANTEE。
  3. 「闪回表比恢复快,所以优先用」。 它的价值是不停机、不影响别人; 单看耗时,小库上恢复可能更快。
  4. 「闪回表不消耗 undo」。 它自己也是一次 DML,退一张大表会把 undo 表空间再撑一轮。
自测三问(点开对答案) 1. 闪回表报 `ORA-08189` 是什么原因? —— **表没开 `ROW MOVEMENT`。** 闪回会改 rowid, **所以要 `ALTER TABLE ... ENABLE ROW MOVEMENT`。** 2. 为什么闪回表要求「表结构没变过」? —— **undo 只记数据,不记结构。** 中途加过列,**那一列在 undo 里没有信息,退回去就对不上。** 3. `RETENTION GUARANTEE` 的代价是什么? —— **undo 表空间满了,事务直接失败。** 也就是「宁可业务报错, 也不让 undo 被覆盖」——**这是明确的取舍,别默认开。** 4. 闪回表和从备份恢复,本质区别在哪? —— **闪回只动你要动的那张表,别的事务照跑;恢复会把整个库退回备份点,别人的改动一起丢。**

下一篇讲闪回丢弃。它管的是「表被 drop 了」这种情况,依据不是 undo 而是回收站。 这是唯一一种不消耗 undo、也不受保留时间限制的闪回。

相关笔记