闪回技术是除了传统备份/恢复之外,Oracle所提供的额外一系列数据保护特性。利用闪回技术,你可以快速查找数据的历史版本,撤销误操作。比起从备份中恢复,闪回操作要简单许多,不通过还原备份就可以将整库回退到指定时间点等。闪回的恢复级别从行级、表级、数据库级均有覆盖。熟练掌握闪回技术可以帮助我们快速解决各种级别的数据问题。
Oracle闪回技术分为以下7类:
一、闪回查询(Flashback Query)
二、闪回版本查询(Flashback Version Query)
三、闪回事务查询(Flashback Transaction Query)
四、闪回事务(Flashback Transaction)
五、闪回表(Flashback Table)
六、闪回删除(Flashback Drop)
七、闪回数据库(Flashback Database)
其中闪回数据库(Flashback Database)属于物理层面特性,其他六项都属于逻辑层面特性。下面将依次演示各项闪回技术。
闪回查询即我们可以在select语句种通过as of子句指定一个历史的时间点或SCN(System Change Number系统变更号),从而查询数据在这个时间点的状态。闪回查询特别适合恢复错误的数据更新/删除,或者通过阶段性的对比观察数据的变化。如果一个表需要能够闪回查询至很久之前(例如5年,10年前甚至永久),可以开启额外的闪回归档(Flashback archive)来存储数据。
闪回查询示例:
我们以HR.employees这张表来演示闪回查询,假设员工ID为206的记录被不小心误删除且已经commit:
此时,我们查询ID为206的员工是不存在的,但是通过as of子句我们可以指定一个历史时间点(比数据误删早一点)或者SCN(提前记录v$database.current_scn的值),我们可以将删除前的数据查出来,后续可以通过insert来撤销误删操作。
select * from employees where employee_id=206;
select * from employees as of timestamp to_timestamp('2021-12-17 02:12:43', 'YYYY-MM-DD HH:MI:SS') where employee_id=206; -- 通过时间点闪回查询
select * from employees as of scn 1634376 where employee_id=206; -- 通过SCN闪回查询
闪回查询可以快速恢复近期的误操作,还有一个常见的用途是我们可以利用闪回查询建立历史版本的视图,方便观察数据变化,例如下面的语句利用闪回查询创建了employees表1小时前的版本视图。
create view hour_ago as select * from employees as of timestamp (systimestamp - interval '60' minute);
闪回版本查询用法和闪回查询类似,闪回查询是返回某一时间点的数据状态,而闪回版本查询则是返回一段时间内数据的所有版本。我们可以用select的versions between子句,通过指定SCN或timestamp时间段来查询一段时间内数据的所有版本(当事务提交的时候,会生成数据的一个版本)。
闪回版本查询示例:
我们依次执行下面2条update语句更新ID为206员工的first_name,并且提交事务。
update employees set first_name='Vincent' where employee_id=206;
commit;
update employees set first_name='Victor' where employee_id=206;
commit;
通过我们预先记录的SCN进行闪回版本查询,我们可以发现在first_name的三个版本全部都查询了出来,William(原始版本) => Vincent => Victor。
select * from employees versions between scn 1693319 and 1693644 where employee_id=206;
通过指定时间段的方式也可以获得相同的结果:
select * from employees
versions between timestamp to_timestamp('2021-12-17 02:59:45', 'YYYY-MM-DD HH24:MI:SS') and to_timestamp('2021-12-17 03:03:17', 'YYYY-MM-DD HH24:MI:SS')
where employee_id=206;
闪回事务查询是通过数据字典视图flashback_transaction_query来查询历史事务相关元数据,并且可以通过该视图的undo_sql字段会给出相应的SQL从逻辑层面撤销事务。要使用flashback_transaction_query查询事务信息首先要打开附加日志记录事务信息:
alter database add supplemental log data;
下面对hr.employees依次开启两个事务,一个update,一个delete.
update hr.employees set salary=10000 where employee_id=206;
commit;
delete from hr.employees where employee_id=206;
commit;
查询事务的详细信息:
我们可以通过闪回版本查询找出hr.employees在一定时间段内的事务ID(XID),并通过XID查询 flashback_transaction_query视图找出对应事务的详细信息。
SELECT *
FROM flashback_transaction_query
WHERE xid IN (
SELECT versions_xid FROM hr.employees VERSIONS BETWEEN TIMESTAMP
TO_TIMESTAMP('2021-12-17 21:10:57', 'YYYY-MM-DD HH24:MI:SS') AND
TO_TIMESTAMP('2021-12-17 21:17:57', 'YYYY-MM-DD HH24:MI:SS') -- where employee_id=206 可以通过更精确的条件限定查询指定行上的事务
);
flashback_transaction_query各列含义:
列名 | 数据类型 | 描述 |
XID | RAW(8) | 事务标识符 |
START_SCN | NUMBER | 事务起始SCN |
START_TIMESTAMP | DATE | 事务起始时间戳 |
COMMIT_SCN | NUMBER | 事务终止SCN (null代表事务正在运行) |
COMMIT_TIMESTAMP | DATE | 事务终止时间戳 (null代表事务正在运行) |
LOGON_USER | VARCHAR2(30) | 事务执行的用户 |
UNDO_CHANGE# | NUMBER | Undo系统变更号 |
OPERATION | VARCHAR2(32) | 事务操作类型 |
TABLE_NAME | VARCHAR2(256) | 事务应用的表名 |
TABLE_OWNER | VARCHAR2(32) | 表的所属用户 |
ROW_ID | VARCHAR2(19) | 被事务修改记录的ROWID |
UNDO_SQL | VARCHAR2(4000) | 事务对应的撤销SQL |
通过undo_sql撤销事务:
查询结果的UNDO_SQL字段提供了相应SQL来撤销该事务。
将UNDO_SQL拷贝出来按提交倒叙撤销事务(执行事务时我们是先update后delete,撤销事务时先insert再update):
insert into "HR"."EMPLOYEES"("EMPLOYEE_ID","FIRST_NAME","LAST_NAME","EMAIL","PHONE_NUMBER","HIRE_DATE","JOB_ID","SALARY","COMMISSION_PCT","MANAGER_ID","DEPARTMENT_ID") values ('206','Victor','Gietz','WGIETZ','515.123.8181',TO_DATE('07-6¿ -02', 'DD-MON-RR'),'AC_ACCOUNT','10000',NULL,'205','110');
update "HR"."EMPLOYEES" set "SALARY" = '8300' where ROWID = 'AAAWZpAAKAAAADPAAJ';
commit;
注意undo_sql是逻辑撤销事务影响,但物理层面上,数据行可能会和以前不同。例如:在撤销delete时,当数据被undo而insert回表时,其物理rowid可能会发生变化。
闪回事务是利用存储过程dbms_flashback.transaction_backout 来回滚某个事务。其回退的原理是利用undo日志逻辑撤销事务的影响,并根据选项决定是否一并撤销依赖其的子事务。
闪回事务有一定的限制条件:
1. 表的结构不能改变(事务提交后未对表进行DDL变更)。
2. 事务中不能使用大字段类型(bfile/blob/clob/nclob)。
3. 无法是用LogMiner不支持的特性。
因为事务之前可能存在依赖性,dbms_flashback.transaction_backout可以通过选项来控制是否一同闪回其依赖事务。
dbms_flashback.transaction_backout语法:
DBMS_FLASHBACK.TRANSACTION_BACKOUT
numtxns NUMBER,
xids XID_ARRAY, -- 通过事务ID列表来闪回
options NUMBER default NOCASCADE, -- 闪回选项,取值可以为cascade/nocascade/nocascade_force/nonconflict_only
timeHint TIMESTAMP default MINTIME); -- timeHint可以替换为scnhint,提示事务开始前的时间或SCN
DBMS_FLASHBACK.TRANSACTION_BACKOUT
numtxns NUMBER,
txnnames TXNAME_ARRAY, -- 通过事务名称列表来闪回
options NUMBER default NOCASCADE, -- 闪回选项,取值可以为cascade/nocascade/nocascade_force/nonconflict_only
timehint TIMESTAMP MINTIME ); -- timeHint可以替换为scnhint,提示事务开始前的时间或SCN
关于dbms_flashback.transaction_backout需要重点注意的是第三项options的值,4个选项的含义如下:
闪回事务示例:
employees表中employee_id为206的记录示例,salary初始值为10000,第一个事务更新为1000,第二个事务在第一个事务基础上将salary乘以2(依赖事务)。
首先通过闪回版本查询,找到记录206在近期的所有事务ID(XID),SCN较小的update即第一个事务。
SELECT xid,commit_scn,commit_timestamp,operation
FROM flashback_transaction_query
WHERE xid IN (
SELECT versions_xid FROM hr.employees VERSIONS BETWEEN TIMESTAMP
TO_TIMESTAMP('2021-12-18 03:35:58', 'YYYY-MM-DD HH24:MI:SS') AND
TO_TIMESTAMP('2021-12-18 03:37:58', 'YYYY-MM-DD HH24:MI:SS')
where employee_id=206); -- 指定查询该条记录上的事务。
通过找到的XID(示例中为'0A00000062050000'),先用默认的选项闪回(nocascade)。因为第二个事务在第一个事务基础上将salary值乘2(闪回的目标事务存在被依赖关系),因此无法单独闪回第一个事务,执行报错。
将选项设置为dbms_flashback.cascade,Oracle会先闪回子事务,再闪回目标事务。可以看到salary值恢复到了初始值10000,事务1和事务2的效果全部都消失了。
闪回事务后,系统并不会自动提交(依然会占有锁,其他会话看不到闪回结果)。 通过查询闪回后的结果,我们发现结果已经恢复到了原始状态,需要显式提交让结果持久化。
以上即是闪回单个事务的示例,多个事务的闪回只需要将xids参数用逗号分隔,numtxns修改为要闪回的事务数量即可。
闪回表的原理是利用undo日志,将整张表回退到某个时间点/SCN/restore_point。该时间点之后的所有变更都会被撤销,而数据库其他的部分不受影响。
闪回的表需要满足的以下前提条件:
闪回表示例:
以employees表示例,初始表中有107行数据,每个员工的salary都不同。
一个事务删除了一行,且更新时未加where条件将salary全部更新为1:
当表上运行了很多事务,采用闪回事务的方式可能相当繁琐,而闪回表则是更好的选择。
首先打开行移动:
alter table hr.employees enable row movement;
执行flashback table命令,可以指定时间点、SCN、或restore point
flashback table hr.employees to scn 1759231;
如果事先未记录SCN,也可以用时间戳闪回至错误发生前:
flashback table hr.employees to timestamp to_timestamp('2021-12-18 03:37:58', 'YYYY-MM-DD HH24:MI:SS');
或使用还原点闪回表:
创建一个普通before_update还原点:
create restore point before_update;
闪回至之前创建的还原点:
flashback table hr.employees to restore point before_update;
还原点可以在一些敏感操作前事先使用create restore point语句建立,方便出错回退。
闪回删除是用来撤销drop table语句的,其原理和windows的回收站相同。当执行drop table语句时,oracle并不会将表删除,而是将表及相关对象重命名并放入回收站。回收站中的对象可以像普通表一样查询。
闪回删除示例:
先创建表t,删除,再创建同名表t,再删除(drop table前会隐式提交数据,因此数据也会在回收站中查到):
使用show recyclebin;我们就可以查看当前回收站中的表,可以看到表的original_name都是T:
show recyclebin;
我们可以像查询普通表一样查询回收站中的对象:
当存在同名表时,使用表名默认会闪回最后drop的表,我们可以用回收站中的对象名指定闪回对象,并可以选择性的进行重命名。
将第一次drop的表闪回,并重名名为t2:
flashback table "BIN$01795tP0Z7/gU9MKqMC7QA==$0" to before drop rename to t2;
此时我们看到表已经恢复了,并且命名为t2。
如果要在drop表时永久性删除,可以附加purge子句,此时表不会进回收站,而是彻底从数据库删除(效果相当于windows下shift+delete)。如果要恢复只能采用其他方法了。
闪回数据库是利用闪回日志撤销近期的变更,将数据库回退到近期的一个时间点。在效果上和时间点恢复(Point-In-Time Recovery)类似,但是闪回数据库不需要还原数据文件和应用redo日志,因此速度快很多。
闪回数据库用的是一套独立的日志系统。闪回日志存储在快速恢复区(Fast Recovery Area,FRA)中,所以开启闪回前要确保FRA配置好,否则无法生成闪回日志。
配置闪回数据库:
1. 首先确定数据库处于归档模式且FRA已经配置:
archive log list; -- 查看归档状态。
show parameter db_recovery_file_dest -- 查看FRA是否配置,因为flashback logs无法存在FRA之外的地方,建议打开闪回数据库后将FRA配置大一点。
2. 打开闪回日志:
确保数据库处于mount或open状态下,打开数据库闪回。
alter database flashback on; -- 开启闪回日志
select flashback_on from v$database; -- 查询v$database.flashback_on查看是否已开启闪回
3. 根据需求调整闪回窗口时长:
参数db_flashback_retention_target定义了闪回日志保存的目标时长(默认是1440分钟,一天),数据库可以闪回至窗口内的任意时间点。
show parameter flashback
配置完成后,数据库就已经开始记录闪回日志了。
闪回至SCN示例:
数据库的闪回目标可以是SCN,时间点,日志序列号或还原点(restore_point)。
闪回至SCN:
1. 首先可以通过v$flashback_database_log查询闪回窗口的最小SCN和时间点,再往前的时间点将无法闪回:
select oldest_flashback_scn, oldest_flashback_time from v$flashback_database_log;
2. 重启数据库至mount状态,使用flashback database to scn,将数据库闪回至指定SCN:
shutdown immediate;
startup mount;
flashback database to scn 1792364;
3. 使用open resetlogs选项打开数据库:
闪回数据库必须使用open resetlogs打开,此选项会创建一个新的数据库化身(incarnation)。
alter database open resetlogs;
闪回至还原点(restore point)示例:
1. 创建还原点:
假设我们需要对数据库进行重大变更,希望失败后可以还原至原始状态,可以在操作前创建一个还原点。
create restore point before_update guarantee flashback database;
guarantee flashback database子句代表创建的是一个guaranteed restore point,如果不加这个子句则创建的是normal restore point。两者不同是normal restore point会随着时间自动删除,而guaranteed restore point必须显式手动删除。数据库将一直保存guaranteed restore point之后产生的闪回日志,即使超过flashback window或关闭闪回日志都不行。数据库将确保能够闪回至这个时间点。所以如果FRA过小或忘记删除,guaranteed restore point可能会导致FRA空间耗尽,数据库挂起。
在RMAN中可以查看我们创建的还原点:
list restore point all;
2. 重启数据库至mount状态,使用flashback database to restore point,将数据库闪回至指定还原点:
shutdown immediate;
startup mount;
flashback database to restore point before_update;
3. 使用open resetlogs选项打开数据库:
alter database open resetlogs;
使用open resetlogs选项打开数据库后将创建一个新的incarnation,相关的信息可以从v$database_incarnation中查询:
select * from v$database_incarnation;
使用闪回数据库撤销上一次resetlogs:
闪回数据库可以跨越incarnation,因此也可以用闪回数据库来撤销open resetlogs影响:
1. 首先确定数据库open resetlogs的SCN在闪回窗口内:
select resetlogs_change# from v$database;
select oldest_flashback_scn from v$flashback_database_log;
可以看到v$database.resetlogs_change#的值大于v$flashback_database_log.oldest_flashback_scn。则说明可以用flashback database 撤销resetlogs。
2. 重启数据库至mount状态,撤销resetlogs:
shutdown immediate;
startup mount;
flashback database to before resetlogs; -- 数据库将会被闪回至紧靠resetlogs之前的SCN。
3. 再次使用open resetlogs打开数据库,上一次open resetlogs之后的所有变更已被撤销。
4.从v$database_incarnation中,我们看到incarnation#为4,5的父级incarnation#都是3。5的status是current(当前),4的status是orphan(孤儿),代表上一次resetlogs已成功撤销。
闪回数据库是利用闪回日志撤销变更,从当前时间点向后退。而备份恢复是将数据库还原至历史备份时间点,然后应用redo日志重放数据库变更(两种技术的还原方向不同)。因此闪回的速度只受变更数据量大小影响。而备份恢复因为要还原整库,受整个数据库大小的影响。虽然2种技术都可以将数据库还原至指定时间点,但闪回数据库具有明显的速度优势。且在不确定闪回是否满足需求时,在open resetlogs之前可以先用open readonly选项打开数据库查看,多次闪回以试探正确的还原时间点。