在Oracle的Undo机制中,参数Undo_Retention扮演了“混淆者”的角色。论坛里面也有很多朋友对这个参数含义不是非常清楚。本篇我们就来讨论一下这个这个参数的含义和作用。

 

Oracle数据库 ORA-01555 快照过旧

用户user1对表进行了更新操作,用户user2在user1还没有进行提交前读表中数据,而且是大批量的读取(打个比方:耗时3分钟)而在这3分钟内user1进行了提交操作,那又会产生什么影响呢?这个时候怎么保证读写一致性呢?这个时候DBMS就要保证有足够大的undo表空间来存放修改前的数值,,以保证user2读取的数据是修改前的一致数据.然后下次再读取时候就是更新后的数据了.

ora-01555快照过旧就是因为undo空间不够大,其中一部分undo数据被覆盖了,用户无法获得修改前的数据。

undo数据分为三种:

活动的undo:未提交事务的undo数据,这些undo数据永远不能覆盖,用于回滚rollback事务。

过期的undo:已提交事务的undo数据,这些undo数据可以覆盖。

未过期的undo:事务已提交,但事务提交前,有些查询正在进行,它要读取的是提交前的数据,这部分数据就是未过期数据。如果这部分undo数据被覆盖了,就会发生ora-01555错误。

一个解决方法是,指定undo表空间参数UNDO_TABLESPACE,并将undo空间管理方法设置成自动扩展:UNDO_MANAGEMENT=AUTO。

这种方法可能产生的结果是:

因为undo表空间装了太多未过期(unexpired)的undo数据,新的transaction无法向其中写入undo数据,这时transaction就会发生ORA-30036错误。

 

1、从Undo说起

Undo或者说rollback segment机制是Oracle早期奠定行业地位的核心技术之一。Undo机制的提出,源自于Oracle提出的“多版本一致读”特性。在Oracle中,select操作不会阻塞任何操作,也不会被任何操作所阻塞。

 

这就意味着,当我们对一个数据表进行DML操作,比如插入、修改和删除数据的时候,其他会话连接的select操作是可以随意进行的,而且访问的数据都是DML操作之前提交的数据。

 

Oracle数据是保存在“表空间、段对象、分区和数据块”的组织结构体系中。对数据的修改就是在数据块中直接的修改。如果需要同时支持对之前数据的访问,比如在系统中有一个地方需要保存数据的“前镜像pre-image”,同时一旦DML进行回滚rollback动作,恢复数据也需要这部分前镜像内容。这就衍生出了rollback segement和之后的Undo表空间。

 

严格的说,多版本一致读是两个Oracle特性。一致读所说的是:当一个查询读操作select数据的时候,只能读取到小于等于启动查询操作时候SCN的数据。比如:我们启动查询的时候,数据表A中包括10亿条数据。之后另一个会话启动删除了1亿条数据并且提交。提交之后,第一个读会话才检索到原来1亿条数据的位置,并且最后结束。从结果看,第一个会话读取到的是10亿条数据。一致读特性的关键就在于保证了读操作的一致性,读取数据在时间层面的一致性。细想一下,第二个会话commit之后,源数据1亿条的前镜像在Undo空间中是失效Expired状态。一致读过程中,一定是进行过失效Undo镜像数据的读取。

 

失效Undo数据的最终归宿是被覆盖重用。想想一种情况,如果系统中Undo管理比较不合理,事务Undo数据量比较大而且频繁,有失效的Undo前镜像被覆盖之后,恰恰有一个长时间查询需要访问这个前镜像。这个时候,Oracle就只能说“抱歉”了,这也就是经典的ora-1555 snapshot too old的起源。

 

Undo失效数据的另一种用途就是Flashback闪回。Flashback Query和Flashback Archive都是建立在对前镜像数据再利用处理上。

Undo_Retention是Oracle提供出的用于控制Undo过期数据保留的“调节Tune”参数。注意:这里不是控制参数。Oracle中的控制参数起到强制作用,比如目录空间位置,归档文件存储等。但是另一部分参数起到的是目标调节的作用,比如检查点间隔控制,SGA_TARGET。

SQL> show parameter undo;

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
undo_management string AUTO
undo_retention integer 900
undo_tablespace string UNDOTBS3

undo_retention从直接看就是设置Oracle Undo过期数据的保存期限,单位是秒。如果参数设置为900秒,那么Undo段数据在非Active状态之后,会保留900秒。

 

如果根据这个守则,我们进行一致读和Flashback的时间就是通过这个参数来进行控制的。但是事实上,这个是有问题的。Undo的覆盖动作是一个必然的过程,覆盖与否是要和系统事务Undo消耗速率、Undo Tablespace大小乃至Undo数据文件可拓展性密切相关。

 

事实上,Undo_retention是一个“目标期望值”。用户设置出这个值之后,Oracle内部会尽量保证将Undo数据保留超过undo_retention设置的时间。在这个过程中,Oracle会涉及到比如尝试拓展Undo表空间数据文件、Undo Segment管理等内容。但是,如果“现实比较残酷”,比如说Undo使用紧张、没有额外的方法,那么这个时间段也是不能保证的。

 

下面我们通过一系列的实验来验证。

 

2、环境准备

SQL> select * from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
PL/SQL Release 11.2.0.4.0 - Production
CORE 11.2.0.4.0 Production
TNS for Linux: Version 11.2.0.4.0 - Production
NLSRTL Version 11.2.0.4.0 - Production

当前采用自动的undo管理机制,默认retention为900秒。注意:这个取值Oracle是有所选择的。早期版本中曾经设置过1800秒。
SQL> show parameter undo;

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
undo_management string AUTO
undo_retention integer 900
undo_tablespace string UNDOTBS3

当前undo tablespace的数据文件拓展情况为支持拓展,容量为100M。
SQL> col FILE_NAME for a30;
SQL> set linesize 1600;
SQL> select FILE_NAME,BYTES/1024/1024 "bytes",MAXBYTES/1024/1024 "maxbytes",AUTOEXTENSIBLE from dba_data_files
2 where TABLESPACE_NAME='UNDOTBS3';

FILE_NAME bytes maxbytes AUT
------------------------------ ---------- ---------- ---
/u01/app/oracle/oradata/oradb/ 100 32767.9844 YES
undotbs03.dbf


3、超过undo_retention情况数据读取
在没有强制的Undo数据覆盖情况下,Oracle默认会一直保留前镜像的数据。在实际中,虽然undo_retention设置的保留值比较小,但是我们依然可以flash back query出几个小时乃至几天前的数据。
为了便于实验过程,我们调节了一下参数。
SQL> alter system set undo_retention=300;

System altered.
SQL> show parameter undo_retention;

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
undo_retention integer 300

此处我们使用的是较大的Undo表空间100M,而且支持自动拓展文件。实验数据表情况如下:
SQL> create table t as select * from dba_objects;

Table created.

SQL> select bytes/1024/1024 MB from dba_segments where OWNER='SYS' and segment_name='T';

MB
----------
1.5390625

SQL> select count(*) from t; --注意:当前时点上,数据量为1万。

COUNT(*)
----------
13524
SQL> select sysdate from dual;

SYSDATE
------------
21-DEC-18

删除数据前查一下系统时间
[root@Database2 ~]# date
Fri Dec 21 13:24:12 CST 2018
删除数据
SQL> delete t;

13524 rows deleted.

SQL> commit;

Commit complete.

经过十五分钟左右,注意这个时间已经远远超过900秒设置值。
[root@Database2 ~]# date
Fri Dec 21 13:37:28 CST 2018

SQL> select count(*) from t as of timestamp to_timestamp('2018-12-21
13:24:12','yyyy-mm-dd hh24:mi:ss'); 2

COUNT(*)
----------
13524

超过了900秒,Undo前镜像依然保留而且可以访问到。到此我们可以得出结论,即使commit后超过了undo_retention设置的时间范围,前镜像数据依然可以读取到。