flashback

9i,10g flashback[@more@]

[Oracle 9i & 10g] Flashback Query, Flashback Version Que...


Admin
2010年6月19日
名人名言:个人如果但靠自己,如果置身于集体的关系之外,置身于任何团结民众的伟大思想的范围之外,就会变成怠惰的、保守的、与生活发展相敌对的人。——高尔基

[Oracle 9i & 10g] Flashback Query, Flashback Version Query, Flashback Table, etc



2009-12-30 18:08
by
fangwen.YU,
133
visits,
网摘, 收藏, 编辑

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