闪回表把一张表退回某个时刻,用的是 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 秒实验
- 把 Demo 里
t81的行数从 20 万改成 2 万,再跑一次。 看 undo 体量和更新耗时各变了多少—— 体会一下「undo 体量跟改的行数成正比、耗时跟数据量也有关」。 - 把
keep从 1000 改成 100,再跑。看「最老的一条是第几次改动之前」怎么变。 - 如果有 Oracle 环境,跑
SELECT * FROM V$UNDOSTAT。MAXQUERYLEN那一列就是当前最长查询已经跑了多少秒——undo_retention至少要大于它。
常见误区
- 「闪回表不需要任何前置条件」。 要
ENABLE ROW MOVEMENT,否则直接ORA-08189。 - 「
undo_retention=900就一定能退 15 分钟」。 它是目标值,undo 不够时会被牺牲; 要真守住得开RETENTION GUARANTEE。 - 「闪回表比恢复快,所以优先用」。 它的价值是不停机、不影响别人; 单看耗时,小库上恢复可能更快。
- 「闪回表不消耗 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、也不受保留时间限制的闪回。
- ☐ (本篇完,下一篇 18.4 闪回丢弃)
相关笔记
- 闪回数据库
- 闪回丢弃
- 使用 RMAN 工具部分恢复
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引