一、背景
本次在 Oracle 19c 测试库 GGMKBAK PDB 中练习 Flashback Query。
模拟场景:
id=2 被误更新
id=3 被误删除
目标:
通过 AS OF TIMESTAMP 查询历史数据,
先创建救援表,
再恢复误删除和误更新的数据。
二、Flashback Query 核心原理
Flashback Query 用来查询表在某个历史时间点的数据状态。
核心语法:
SELECT *
FROM 表名 AS OF TIMESTAMP
TO_TIMESTAMP('历史时间点', 'YYYY-MM-DD HH24:MI:SS')
WHERE 条件;
简单理解:
当前表数据已经错了,
但只要对应的 UNDO 没有被覆盖,
Oracle 就可以查到过去某个时间点的数据。
本文重点使用:
AS OF TIMESTAMP
因为生产环境中通常只知道大概误操作时间,不一定提前记录 SCN。
三、Flashback Query 主要依赖 UNDO
Flashback Query 主要依赖 UNDO,不需要额外开启 Flashback Database。
本次环境检查:
ALTER SESSION SET CONTAINER=GGMKBAK;
SHOW CON_NAME;
SHOW PARAMETER undo;
结果类似:
undo_management = AUTO
undo_retention = 900
undo_tablespace = UNDOTBS1
说明:
| 参数 | 含义 |
|---|---|
undo_management=AUTO |
自动 UNDO 管理 |
undo_retention=900 |
UNDO 目标保留时间 900 秒 |
undo_tablespace=UNDOTBS1 |
当前使用的 UNDO 表空间 |
四、重点:undo_retention=900 为什么过了两三个小时还能查?
undo_retention=900 的单位是秒:
900 秒 = 15 分钟
但它不是"15 分钟后立刻删除"。
准确理解:
UNDO_RETENTION 是目标保留时间,不是强制删除时间。
超过 900 秒的 UNDO 可以被复用,但不会马上清除。
只要旧 UNDO 没有被新事务覆盖,Flashback Query 仍然可能查到。
所以测试库过了两三个小时还能查,通常是因为:
1. 测试库事务量小
2. UNDO 压力低
3. 旧 UNDO 虽然过期,但还没被覆盖
4. Oracle 可能自动调优了实际 UNDO 保留时间
可以查看 UNDO 状态:
SELECT tablespace_name,
status,
ROUND(SUM(bytes)/1024/1024, 2) AS mb
FROM dba_undo_extents
GROUP BY tablespace_name, status
ORDER BY tablespace_name, status;
状态说明:
| 状态 | 含义 |
|---|---|
ACTIVE |
当前事务正在使用 |
UNEXPIRED |
未过保留期 |
EXPIRED |
已过保留期,可以被复用 |
| 已被覆盖 | 历史数据不可查 |
重点记住:
EXPIRED 只是可以被复用,不代表已经被删除。
五、创建测试表并插入原始数据
DROP TABLE fbq_test PURGE;
CREATE TABLE fbq_test (
id NUMBER PRIMARY KEY,
name VARCHAR2(30),
amount NUMBER,
remark VARCHAR2(100),
create_time TIMESTAMP DEFAULT SYSTIMESTAMP
);
INSERT INTO fbq_test(id, name, amount, remark)
VALUES (1, 'A', 100, '原始数据');
INSERT INTO fbq_test(id, name, amount, remark)
VALUES (2, 'B', 200, '原始数据');
INSERT INTO fbq_test(id, name, amount, remark)
VALUES (3, 'C', 300, '原始数据');
COMMIT;
SELECT * FROM fbq_test ORDER BY id;
原始数据:
1 A 100 原始数据
2 B 200 原始数据
3 C 300 原始数据
六、模拟误更新和误删除
UPDATE fbq_test
SET amount = 9999,
remark = '误更新'
WHERE id = 2;
DELETE FROM fbq_test
WHERE id = 3;
COMMIT;
SELECT * FROM fbq_test ORDER BY id;
此时当前表变成:
1 A 100 原始数据
2 B 9999 误更新
说明:
id=2 被误更新
id=3 被误删除
七、使用 Flashback Query 查询历史数据
假设历史正确时间点是:
2026-05-25 11:30:00
执行:
SELECT *
FROM fbq_test AS OF TIMESTAMP
TO_TIMESTAMP('2026-05-25 11:30:00', 'YYYY-MM-DD HH24:MI:SS')
ORDER BY id;
查询结果:
1 A 100 原始数据
2 B 200 原始数据
3 C 300 原始数据
说明当前数据虽然错了,但历史正确数据还能通过 UNDO 查到。
八、先创建救援表
生产环境不要直接恢复,先把历史数据固定下来。
DROP TABLE fbq_test_rescue PURGE;
CREATE TABLE fbq_test_rescue AS
SELECT *
FROM fbq_test AS OF TIMESTAMP
TO_TIMESTAMP('2026-05-25 11:30:00', 'YYYY-MM-DD HH24:MI:SS');
SELECT * FROM fbq_test_rescue ORDER BY id;
救援表中保存的是历史正确数据。
这样做的好处:
一旦救援表创建完成,
即使后续 UNDO 被覆盖,
也不影响恢复。
九、恢复误删除的数据
恢复被删除的 id=3:
INSERT INTO fbq_test(id, name, amount, remark, create_time)
SELECT id, name, amount, remark, create_time
FROM fbq_test_rescue
WHERE id = 3;
COMMIT;
十、恢复误更新的数据
恢复 id=2 的错误字段:
UPDATE fbq_test t
SET (amount, remark) = (
SELECT r.amount, r.remark
FROM fbq_test_rescue r
WHERE r.id = t.id
)
WHERE t.id = 2;
COMMIT;
最终验证:
SELECT * FROM fbq_test ORDER BY id;
恢复后:
1 A 100 原始数据
2 B 200 原始数据
3 C 300 原始数据
十一、补充:MERGE 一次性处理误更新和误删除
除了分别使用 INSERT 恢复误删除、使用 UPDATE 恢复误更新,也可以使用 MERGE 一次性处理。
MERGE 可以理解为:
有则更新,无则插入。
也就是:
UPDATE + INSERT = MERGE
适合这种场景:
id=2 当前表中还存在,但是数据被误更新,需要 UPDATE;
id=3 当前表中已经不存在,需要 INSERT。
可以使用下面 SQL 一次性恢复:
MERGE INTO fbq_test t
USING (
SELECT *
FROM fbq_test_rescue
WHERE id IN (2, 3)
) r
ON (t.id = r.id)
WHEN MATCHED THEN
UPDATE SET
t.name = r.name,
t.amount = r.amount,
t.remark = r.remark,
t.create_time = r.create_time
WHEN NOT MATCHED THEN
INSERT (id, name, amount, remark, create_time)
VALUES (r.id, r.name, r.amount, r.remark, r.create_time);
COMMIT;
这段 SQL 的逻辑是:
如果 fbq_test 中存在相同 id,就用救援表的数据更新;
如果 fbq_test 中不存在这个 id,就从救援表插入回来。
本次实验中:
id=2 存在,所以执行 UPDATE;
id=3 不存在,所以执行 INSERT。
学习阶段建议先掌握:
误删除用 INSERT;
误更新用 UPDATE;
批量混合恢复再考虑 MERGE。
生产环境使用 MERGE 时一定要加条件,例如:
WHERE id IN (2, 3)
避免把救援表中的所有数据都回写到正式表,造成二次事故。
十二、生产使用建议
生产环境中通常只知道大概误操作时间,可以这样处理:
1. 确认大概异常时间
2. 使用 AS OF TIMESTAMP 查询历史数据
3. 多试几个时间点,找到正确快照
4. 创建救援表
5. 备份当前错误数据
6. 对比确认
7. 再执行 INSERT / UPDATE / MERGE 恢复
示例:
CREATE TABLE 业务表_RESCUE_20260525 AS
SELECT *
FROM 业务表 AS OF TIMESTAMP
TO_TIMESTAMP('2026-05-25 11:25:00', 'YYYY-MM-DD HH24:MI:SS')
WHERE 条件;
生产一定要加 WHERE 条件,避免全表误恢复。
十三、常见报错
| 报错 | 原因 |
|---|---|
ORA-01555: snapshot too old |
需要的历史 UNDO 已被覆盖 |
ORA-08180 |
指定时间太早或时间点不可用 |
ORA-01466 |
历史时间点到当前之间发生过 DDL |
十四、总结
Flashback Query 的核心就是:
AS OF TIMESTAMP + UNDO
重点记住:
1. Flashback Query 用来查询历史时间点数据。
2. 它主要依赖 UNDO。
3. undo_retention=900 表示目标保留 15 分钟。
4. 15 分钟后不是立刻删除,只是可以被复用。
5. 只要旧 UNDO 没被覆盖,就仍然可能查到。
6. 生产恢复时先建救援表,再恢复数据。
7. 误删除用 INSERT,误更新用 UPDATE。
8. 混合恢复场景可以使用 MERGE。
DBA 口诀:
AS OF 看过去,
UNDO 存记忆;
Retention 是目标,
没覆盖就能查;
先救援表落地,
再恢复别着急。