Oracle 9i 引入了flashback
query,可以方便我们查询过去一个时间“点”的数据库的状态。比如我们在T1时刻更改了一条记录,接着在T2时刻删除了该条数据(并且commit了),我们可以通过flashback
query来查询到T1时刻的该条记录。
Oracle 10g把flashback query推进了一步,不光可以查询过去一个时间“点”的数据库状态,
而且可以查询过去一段时间内数据状态变化的过程(不同版本), 所以叫做flashback version query。
不管是flashback query还是flashback version query, 所有的这些“历史数据”都保存在了Undo Segment上。
(在Orace 9i之前叫做rollback segment,
对应于手动来管理这部分空间)因此,我们可以查询到的历史数据受限于undo segment的大小(不太准确),因为undo
segment在空间不够的时候会被覆写。当然,可以设置参数UNDO_RETENTION来设置数据在Undo Segment上的“存活”时间(单位是秒)。
那么flashback query有啥好处呢? 这是个很容易想到的问题。
我觉得一个好处就是方便我们来undo我们之前做的“误”操作,比如我们不小心删除了一条数据并且commit了,很显然用rollback已经不起作用了,这个时候我们可以通过flashback
query找到那个时刻的数据,把它重新插入到数据库中。(或者通过flashback table来直接恢复到之前的数据状态)
那么我们如何查询到过去某个时刻的数据(从undo segment上)呢, Oracle
9i支持两种方式,一种是SCN(system change
number),另外一种是timestamp。 Oracle10g R2 增加了一种方式,叫做restore
point, 这个接下来都会介绍到。
提到flashback, 可能会容易想到oracle 10g引入了flash recovery
area(FRA)的概念,这个跟flashback query没有多大关系,FRA主要跟RMAN,数据库的恢复有关,具体可以参见这里。
同样,提到flashback, 还会想到oracle 10g R1 引入的flashback table。
从10g开始,用drop语句来删除一个数据库对象并不会直接把该数据库对象从数据库中彻底删除掉,而是把该数据库对象改了个名字放到了recyclebin中,
当然如果想彻底删除该数据库对象,可以在drop 的时候加上purge, 比如 drop table XXX
purge,这样改数据库对象就不会放到recyclebin中,而是直接被删除了。正是因为数据库对象暂时保存在了recyclebin中,我们可以通过flashback语句来把该数据库对象恢复过来,例如
flashback table XXX to before drop. 对于这部分内容,最后再说,先从flashback
query 谈起。
1. Flashback Query in Oracle 9i (参考Adrian Billington的Flashback Query in Oracle 9i)
Flashback query 在9.1 和9.2还是有点区别的,9.1的用法比较繁琐,不方便,9.2改进了许多。
(1) 应用flashback query的先决条件
undo_management = auto
(在参数文件pfile/spfile中设置)
undo_retention = n ( n是个正整数,单位是sec,
也是在参数文件pfile/spfile中设置)
undo_tablespace = (也是在参数文件中设置)
FLASHBACK 或者 FLASHBACK
ANY的系统权限(system priviledge)
执行DBMS_FLASHBACK的权限
(2) 示例数据
SQL> create table t
2 as
3 select owner,
table_name, tablespace_name
4 from all_tables
5 where rownum <=
5;
Table created.
SQL> select * from t;
OWNER TABLE_NAME
TABLESPACE_NAME
-------------------- ------------------------------
----------------------
--
SYS
ICOL$ SYSTEM
SYS
CON$ SYSTEM
SYS
UNDO$ SYSTEM
SYS
PROXY_ROLE_DATA$ SYSTEM
SYS
FILE$ SYSTEM
SQL>
【提示】在Oracle9i(9.1 和 9.2)中,Oracle建议我们等待大约5分钟之后再进行flashback
query新创建的表,否则可能会碰到这个错误ORA-01466: unable to read data - table definition has
changed. 注意这个5分钟时间间隔正好是9i 中SCN和timestamp相map的时间间隔,虽然可以用timestamp来进行flashback
query,但实质还是根据该timestamp对应的SCN来进行flasbback query, 由于timestamp不是实时地跟SCN对应上,
因此我们需要等段时间来让timestamp跟SCN同步上, 因此用SCN会比timestamp更准确些。注意SCN会在每个commit的时候增长。
(3) 9.1: DBMS_FLASHBACK
在Oracle9i中,需要用到package DBMS_FLASHBACK来enable/disable FLASHBACK query。
在进行操作之前,首先得到当前的SCN以便之后flashback query到这个时刻的数据,
SQL> select dbms_flashback.get_system_change_number as
scn from dual;
SCN
----------
1517851
SQL>
接下来对刚才创建的Table t做一些更改(并commit),
SQL> update t set tablespace_name =
LOWER(tablespace_name);
5 rows updated.
SQL> commit;
Commit complete.
SQL>
可以看到数据发生了变化
SQL> select * from t;
OWNER TABLE_NAME
TABLESPACE_NAME
-------------------- ------------------------------
------------------------
--
SYS
ICOL$ system
SYS
CON$ system
SYS
UNDO$ system
SYS
PROXY_ROLE_DATA$ system
SYS
FILE$ system
SQL>
现在我们可以使用flashback
query来得到我们更改之前的数据。(需要先用DBMS_FLASHBACK.ENABLE_AT_SYSTEM_CHANGE_NUMBER(scn)
来把数据库设置成flashback 状态,之后我们就会看到这个SCN时的数据库状态)
SQL> exec
DBMS_FLASHBACK.ENABLE_AT_SYSTEM_CHANGE_NUMBER(1517851);
PL/SQL procedure successfully completed.
SQL> select * from t;
OWNER TABLE_NAME
TABLESPACE_NAME
-------------------- ------------------------------
------------------
--
SYS
ICOL$
SYSTEM
SYS
CON$
SYSTEM
SYS
UNDO$
SYSTEM
SYS PROXY_ROLE_DATA$
SYSTEM
SYS
FILE$ SYSTEM
SQL>
可以看到tablespace_name的数据都变成更改之前的样子了。
如果想看到数据库“实际”的状态,我们需要调用DBMS_FLASHBACK.DIABLE来离开flashback mode.
SQL> exec DBMS_FLASHBACK.DISABLE;
PL/SQL procedure successfully completed.
SQL> select * from t;
OWNER TABLE_NAME
TABLESPACE_NAME
-------------------- ------------------------------
-------------------
--
SYS ICOL$
system
SYS CON$
system
SYS UNDO$
system
SYS PROXY_ROLE_DATA$
system
SYS FILE$
system
SQL>
(4) 9.2 as of [SCN |
timestamp]
Oracle 9.2引入了新的机制来实现flashback query,
而不是9.1中那样需要手动去调用dbms_flaback中的存储过程。在Oracle 9.2中,我们可以直接通过SQL语句来实现,只需要加入(AS OF SCN xxx 或者 AS OF TIMESTAMP xxx) 就可以了。
现在来看看刚才在9.1中的做法,如何在9.2中来实现,(这次更改table_name).
SQL> alter session set NLS_TIMESTAMP_FORMAT =
"DD-MON-YYYY HH24:MI:SS.FF3";
Session altered.
SQL> select localtimestamp from dual;
LOCALTIMESTAMP
---------------------------------------------------------------------------
30-DEC-2009
10:51:11.656
SQL> select * from t;
OWNER TABLE_NAME
TABLESPACE_NAME
-------------------- ------------------------------
---------------------------
--
SYS
ICOL$ system
SYS
CON$ system
SYS
UNDO$ system
SYS
PROXY_ROLE_DATA$ system
SYS
FILE$ system
SQL> update t set table_name = lower(table_name);
5 rows updated.
SQL> commit;
Commit complete.
SQL> select * from t;
OWNER TABLE_NAME
TABLESPACE_NAME
-------------------- ------------------------------
---------------------------
SYS
icol$ system
SYS
con$ system
SYS
undo$ system
SYS
proxy_role_data$ system
SYS
file$ system
现在可以通过AS OF TIMESTAMP 来实现flashback query了。
SQL> select * from t
2 AS OF
TIMESTAMP to_timestamp("30-DEC-2009 10:51:11.656");
OWNER TABLE_NAME
TABLESPACE_NAME
-------------------- ------------------------------
------------------
--
SYS ICOL$
system
SYS CON$
system
SYS UNDO$
system
SYS PROXY_ROLE_DATA$
system
SYS FILE$ system
很显然,这比在oracle 9.1中简单地多。
2. Flashback Version Query in oracle 10g (参考Adrian
Billington的 flashback version query in oracle 10g)
(1) 先创建需要的示例数据,
SQL> create table fbt
2 ( x integer,
3 y
timestamp,
4 z varchar2(30));
Table created.
SQL> exec dbms_lock.sleep(10);
PL/SQL procedure successfully completed.
SQL> insert into fbt values(1, LOCALTIMESTAMP,
"Initial population");
1 row created.
SQL> commit;
Commit complete.
【注意】注意,这里用dbms_lock sleep了10秒钟,然后才插入数据(其实没啥关系,我认为)。
Oracle建议这么做,是为了避免遇到错误ORA-01466: unable to read data - table definition has
changed。这个错误只有当flashback back query针对该刚刚建立的表执行时才会出现。
(2) 对数据进行一系列的更改,并执行commit
SQL> alter session set NLS_TIMESTAMP_FORMAT =
"DD-MON-YYYY HH24:MI:SS.FF3";
Session altered.
SQL> select LOCALTIMESTAMP as lower_bound from
dual;
LOWER_BOUND
---------------------------------------------------------------------------
30-DEC-2009
11:35:03.981
SQL> update fbt set y = LOCALTIMESTAMP, z = "First
update";
1 row updated.
SQL> commit;
Commit complete.
SQL> update fbt set y = LOCALTIMESTAMP, z = "Second
update";
1 row updated.
SQL> commit;
Commit complete.
SQL> select LOCALTIMESTAMP as upper_bound from
dual;
UPPER_BOUND
---------------------------------------------------------------------------
30-DEC-2009
11:36:32.999
SQL> delete from fbt where x = 1;
1 row deleted.
SQL> commit;
Commit complete.
SQL> select * from fbt;
no rows selected
(3) Flashback version query
虽然现在表fbt里面没有数据,但是可以通过flashback version query来查到之前的“历史变化”数据。 Flashback version
query是通过from语句的扩展语句 versions between. 有两种形式的versions between
VERSIONS BETWEEN TIMESTAMP [lower bound] AND
[upper bound]
VERSIONS BETWEEN SCN [lower bound] AND [upper
bound]
lower bound/ upper bound 可以是具体的timestamp/scn, 也可以是关键字minvalue/maxvalue.
这些关键字让Oracle去找到所有的versions, 当然这要受制于undo_retention参数设置的大小,毕竟这部分信息是放在undo
segment上的。
SQL> select x, y, z
2 from fbt VERSIONS BETWEEN
TIMESTAMP MINVALUE AND MAXVALUE
3 order
by y;
X
Y Z
----------
------------------------------ ------------------------------
1
30-DEC-2009 11:33:58.667 Initial population
1 30-DEC-2009
11:35:27.374 First update
1 30-DEC-2009 11:36:15.373
Second update
1 30-DEC-2009 11:36:15.373
Second update
从结果可以看出,之前做的操作都去取出来了,但是注意Second update出现了两条记录,这个有点奇怪。 还好flashback version
query提供了一些pseudo-columns来帮助我们分析,参见下一部分。
(3) Versions pseudo-columns
Oracle 10g提供了一些pseudo-columns来帮助我们分析每个version的数据,
我们可以通过查询这些pseudo columns来获知每个版本的数据信息,
SQL>
1 select z,
2 versionS_starttime,
3
versionS_endtime,
4 versionS_xid,
5 versionS_operation
6
from fbt VERSIONS BETWEEN TIMESTAMP MINVALUE AND MAXVALUE
7* order by
versions_endtime
SQL> /
Z
VERSIONS_STARTTIME VERSIONS_ENDTIME
VERSIONS_XID
VERSIONS_OPERATION
------------------------------
------------------------------ ------------------------------
------------------------------ -------------------
Initial
population 30-DEC-2009 11:33:57.000 30-DEC-2009
11:35:39.000 06001B004C030000 I
First
update 30-DEC-2009 11:35:39.000 30-DEC-2009
11:36:18.000 0900100063030000 U
Second
update 30-DEC-2009 11:36:18.000 30-DEC-2009
11:36:54.000 0100250048030000 U
Second
update 30-DEC-2009
11:36:54.000 0900070063030000
D
从这里可以看到,两条Second update 其实是对应两个操作,一个是update (U), 还有一个是Delete (D)。
从这个记录同时可以看出删除操作是最后一步,因为对应的VERSIONS_ENDTIME是NULL。
(4) Flashback transaction query
Oracle 还提供了一个新的视图, FLASHBACK_TRANSACTION_QUERY 来提供更多的关于data
version的信息。查询这个视图需要SELECT ANY TRANSACTION 的系统权限。
SQL> desc flashback_transaction_query
Name Null? Type
----------------------------- -------- --------------------
XID RAW(8)
START_SCN NUMBER
START_TIMESTAMP DATE
COMMIT_SCN NUMBER
COMMIT_TIMESTAMP DATE
LOGON_USER VARCHAR2(30)
UNDO_CHANGE# NUMBER
OPERATION VARCHAR2(32)
TABLE_NAME VARCHAR2(256)
TABLE_OWNER VARCHAR2(32)
ROW_ID VARCHAR2(19)
UNDO_SQL VARCHAR2(4000)
(5) Timestamp 和 SCN 之间的转换
Oracle 10g提供了两个函数TIMESTAMP_TO_SCN和SCN_TO_TIMESTAMP来进行timestamp 和 scn之间的转换。
SCN 可以通过dbms_flashback.get_system_change_number来得到,
timestamp可以通过localtimestamp和systimestamp来获得。
SQL> alter session set
NLS_TIMESTAMP_FORMAT="DD-MON-YYYY HH24:MI:SS.FF3";
Session altered.
SQL> select SYSTIMESTAMP, LOCALTIMESTAMP,
2
DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER as scn
3 from dual;
SYSTIMESTAMP
LOCALTIMESTAMP
SCN
---------------------------------------------------------------------------
------------------------
---------------------------------------------------
----------
30-DEC-09 03.18.41.955000 PM
+08:00 30-DEC-2009
15:18:41.955
1537555
SQL> select SCN_TO_TIMESTAMP(1537555)
as ts from dual;
TS
---------------------------------------------------------------------------
30-DEC-2009
15:18:39.000
SQL> select TIMESTAMP_TO_SCN("30-DEC-2009
15:18:39.000") from dual;
TIMESTAMP_TO_SCN("30-DEC-200915:18:39.000")
-------------------------------------------
1537555
3. Flashback table in 10g (参考Adrian Billington的 flashback table in 10g)
Oracle 10g 引入了flashback table 和 flashback database, 这里只cover flashback
table部分。
Flashback table 跟 Flashback query的区别在于, flashback
table会把表中的数据永久地恢复到之前的状态,而flashback query并没有对表中的数据进行恢复,而只是返回之前时刻的数据。
(1) 准备工作
SQL> drop table t purge;
Table dropped.
SQL> create table t
2 nologging
3
enable row movement
4 as
5 select object_type as
x
6 from all_objects;
Table created.
为了可以把一个表flashback to 之前的一个状态,需要满足两个条件
> 该表必须是enable row movement的, 因为flashback
一个表的时候,表中每条记录的rowid会发生变化。这可以通过alter table或在创建表的时候指定,如上例。
> 执行操作的user要有FLASHBACK ANY TABLE的系统权限,或者目标表被赋予了改权限。
创建好表之后,最好等几分钟让SCN增加。如果尝试去flashback一个新创建的表,可能会遇到ORA-01466: unable to read
data - table definition has changed
(2) Flashback to a previous state
Flashback table主要是一个数据库恢复的方法,一般用来恢复被误删除的表中数据或整个表。
首先删除表t中记录为PACKAGE的数据,并commit,
SQL> delete from t where x = "PACKAGE";
1204 rows deleted.
SQL> commit;
Commit complete.
SQL> select count(*) from t where x = "PACKAGE";
COUNT(*)
----------
0
现在可以通过flashback table来让表t回到之前的状态, (两分钟之前)
SQL> flashback table t to TIMESTAMP systimestamp -
interval "2" minute;
Flashback complete.
SQL> select count(*) from t where x = "PACKAGE";
COUNT(*)
----------
1204
我们也可以用SCN来让flashback table回到之前的某个SCN状态,
SQL> var scn number;
SQL> begin
2 :scn
:= TIMESTAMP_TO_SCN(SYSTIMESTAMP);
3 end;
4 /
PL/SQL procedure successfully completed.
SQL> delete from t where x = "PACKAGE";
1204 rows deleted.
SQL> select count(*) from t where x = "PACKAGE";
COUNT(*)
----------
0
SQL> FLASHBACK TABLE t to SCN :scn;
Flashback complete.
SQL> select count(*) from t where x = "PACKAGE";
COUNT(*)
----------
1204
SQL>
(3) flashback data, not structure
需要注意的是,flashback table只是恢复数据,并不会对表的schema的变化有啥影响。
给表t增加一列new_column,
SQL> alter table t add new_column integer default 0;
Table altered.
SQL> update t set x = "MODIFIED";
61883 rows updated.
SQL> commit;
Commit complete.
SQL> select * from t where rownum <= 10;
X NEW_COLUMN
-------------------
----------
MODIFIED 0
MODIFIED
0
MODIFIED 0
MODIFIED
0
MODIFIED 0
MODIFIED
0
MODIFIED 0
MODIFIED
0
MODIFIED 0
MODIFIED 0
10 rows selected.
flashback table到2分钟之前,
SQL> flashback table t to timestamp (systimestamp -
interval "2" minute);
Flashback complete.
SQL> select * from t where rownum <= 10;
X NEW_COLUMN
-------------------
----------
TABLE
INDEX
TABLE
PARTITION
TABLE
INDEX
TABLE
INDEX
TABLE
PARTITION
TABLE
INDEX
10 rows selected.
可以看到列x中的数据恢复到之前的状态吗,但是新增加的列new_column还是在的,并没有消失(虽然该列的数据都没有了)。
那么Oracle是如何来实现这个flashback table的feature的呢?
可以通过下面的SQL语句查看新创建的数据对象有哪些,
SQL> select owner, object_name, object_type,
temporary
2 from dba_objects
3 where created > trunc(sysdate);
OWNER OBJECT_NAME OBJECT_TYPE
T
--------------------
-------------------------------------------------------------------------
FRANK
SYS_TEMP_FBT
TABLE YFRANK
FBT
TABLE N
FRANK
T
TABLE N
抛开表FBT不看(上一个部分中用到的这个表),除了表T,还有一个global temporary table
SYS_TEMP_FBT. 这个表显然是Oracle自己创建的。
该表结构如下,
SQL> desc
SYS_TEMP_FBT;
Name
Null?
Type
---------------------------------------------------------------------------------------------
SCHEMA
VARCHAR2(32)
OBJECT_NAME
VARCHAR2(32)
OBJECT#
NUMBER
RID
ROWID
ACTION
CHAR(1)
其中一部分数据如下 (用Sample语句),
SQL> select * from sys_temp_fbt sample(0.01);
SCHEMA OBJECT_NAM OBJECT# RID
A
---------- ---------- ---------- -------------------- -
FRANK
T 70534 AAAROGAAIAAAABKAGN D
FRANK T 70534
AAAROGAAIAAAABTAHc I
FRANK T 70534 AAAROGAAIAAAABVAHK
I
FRANK T 70534 AAAROGAAIAAAABqAEX I
FRANK
T 70534 AAAROGAAIAAAACOAA8 D
FRANK T 70534
AAAROGAAIAAAAEPAEy D
FRANK T 70534 AAAROGAAIAAAAEbACE
I
FRANK T 70534 AAAROGAAIAAAAEbACL I
FRANK
T 70534 AAAROGAAIAAAAE2AKF D
FRANK T 70534
AAAROGAAIAAAAFDAEc I
10 rows selected.
这个表中的数据是在进行flashback table操作的时候产生的。其实这个表(sys_temp_fbt)也是在第一次进行flashback table
操作的时候生成的。
SQL> select action, count(*) from sys_temp_fbt group
by action;
A COUNT(*)
- ----------
I 61883
D
61883
SQL> select count(*) from t;
COUNT(*)
----------
61883
根据Action (I -- insert, D-- Delete)的行数,可以看出,每次flashback
table的时候,oracle会先把数据删除,然后再重新插入之前时刻的数据(通过flashback query).
重新创建个小表来再看下,如下
SQL>
1 create table test
2 enable row movement 3 as
4 select
rownum r
5 from dual
6* connect by rownum <= 10
SQL> /
Table created.
SQL> select * from test;
R
----------
1
2
3
4
5
6
7
8
9
10
10 rows selected.
删除4条记录,剩余6条。
SQL> delete from test where rownum < 5;
4 rows deleted.
SQL> commit;
Commit complete.
进行flashback table操作
SQL> flashback table test to timestamp systimestamp -
interval "1" minute;
Flashback complete.
SQL> select * from test;
R
----------
1
2
3
4
5
6
7
8
9
10
10 rows selected.
SQL> select object_name, object_type, temporary from
dba_objects
2 where created > trunc(sysdate);
OBJECT_NAM OBJECT_TYPE T
----------
------------------- -
SYS_TEMP_F TABLE Y
BT
TEST TABLE N
FBT
TABLE N
T TABLE N
可以看出SYS_TMEP_FBT只存在一张,而且其中的数据随着每次flashback table操作而重置。
SQL> select action, count(*) from sys_temp_fbt group
by action;
A COUNT(*)
- ----------
I
10
D 6
SQL> select * from sys_temp_fbt order by rid;
SCHEMA OBJECT_NAM OBJECT# RID
A
---------- ---------- ---------- -------------------- -
FRANK
TEST 70724 AAARREAAIAAAACUAAA I
FRANK TEST 70724
AAARREAAIAAAACUAAB I
FRANK TEST 70724 AAARREAAIAAAACUAAC
I
FRANK TEST 70724 AAARREAAIAAAACUAAD I
FRANK
TEST 70724 AAARREAAIAAAACUAAE D
FRANK TEST 70724
AAARREAAIAAAACUAAE I
FRANK TEST 70724 AAARREAAIAAAACUAAF
D
FRANK TEST 70724 AAARREAAIAAAACUAAF I
FRANK
TEST 70724 AAARREAAIAAAACUAAG D
FRANK TEST 70724
AAARREAAIAAAACUAAG I
FRANK TEST 70724 AAARREAAIAAAACUAAH
D
SCHEMA OBJECT_NAM OBJECT# RID
A
---------- ---------- ---------- -------------------- -
FRANK
TEST 70724 AAARREAAIAAAACUAAH I
FRANK TEST 70724
AAARREAAIAAAACUAAI D
FRANK TEST 70724 AAARREAAIAAAACUAAI
I
FRANK TEST 70724 AAARREAAIAAAACUAAJ D
FRANK
TEST 70724 AAARREAAIAAAACUAAJ I