Oracle Flashback Query 实战练习:误更新、误删除数据如何快速找回?

一、背景

本次在 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 是目标,
没覆盖就能查;
先救援表落地,
再恢复别着急。
相关推荐
衍生星球5 小时前
SpringBoot3 + Vue3 + 微信小程序如何学习,以及学哪些技术,组件
sql·微信小程序·uni-app·vue·springboot
心心喵5 小时前
[linux] nohup和pm2的区别 进程保活
linux·运维·服务器
Database_Cool_5 小时前
向量检索加速首选:阿里云 Tair 内置向量能力毫秒级召回
数据库·阿里云
强健的石头6 小时前
django文件对象是什么?
数据库·django·sqlite
NGINX开源社区6 小时前
F5 NGINX Ingress Controller 5.3.0 新增功能
运维
醉熏的石头6 小时前
Ubuntu 中的编程语言(中)
linux·ubuntu·scala
ClouGence6 小时前
保障公共安全,一线治安机构实时数据应用与实践
数据库·mysql·postgresql
烟漠河洛7 小时前
线上事故复盘:Redis幂等性设计边界没覆盖跨状态请求,订单状态机直接崩了
数据库·redis·缓存
Minxinbb7 小时前
TDSQL for MySQL 服务器重启操作
数据库·mysql·dba
SelectDB7 小时前
Doris 实战:用 Colocate Join 消除 Shuffle,AB 指标计算性能提升 145 倍 🚀
数据库·性能优化·ab测试