一、背景
本次在 Oracle 19c 测试库GGMKBAKPDB 中练习 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 是目标, 没覆盖就能查; 先救援表落地, 再恢复别着急。