接上文,在这一节中我们来观察一下使用TAF发生故障转移时,Oracle让Select语句能够继续执行的一些现象。
测试环境跟上文一样,但是TNSNAME配置有些变化,增加了“RETRY”和“DELAY”,增加的选项是为了避免在failover时,如果正在执行SQL,客户端报ORA-03113错误:
XTY =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.114)(PORT = 1521))
)
(CONNECT_DATA =
(SERVICE_NAME = XTY)
(FAILOVER_MODE =
(TYPE = SELECT)
(METHOD = BASIC)
(RETRIES = 180) (DELAY = 5)
)
)
)
首先,我们在TEST用户下建一个测试的表,同时将系统的sql_trace参数设置为TRUE:
SQL> create table test.t1 as select * from dba_objects;
Table created.
SQL> alter system set sql_trace=true scope=spfile;
System altered.
在客户端,使用sqlplus连接数据库,连接的用户名为TEST。连接后,执行下面的语句:
SQL> spool 1.txt
SQL> select rownum,object_id,owner,object_name from t1 where rownum< =5000 order by object_id;
在客户端显示输出的过程中,在另一个会话重启数据库:
SQL> startup force;
ORA-32004: obsolete and/or deprecated parameter(s) specified
ORACLE instance started.Total System Global Area 167772160 bytes
Fixed Size 1266392 bytes
Variable Size 100666664 bytes
Database Buffers 62914560 bytes
Redo Buffers 2924544 bytes
Database mounted.
Database opened.
正在查询T1表的会话,在数据库重启时,会短暂地停止,在我的测试中,停止时显示的输出为:
1140 1186 SYS V_$WAITSTAT
在数据库重启完成后,sqlplus继续输出数据,直到完成所有输出:
1094 1140 SYS V_$LOGHIST
1095 1141 PUBLIC V$LOGHIST
1096 1142 SYS V_$SQLAREA
......(输出很多,略过).....
我们在生成的sql trace文件中,可以看到下面的内容:
PARSING IN CURSOR #3 len=87 dep=0 uid=55 oct=3 lid=55 tim=1208634047724259 hv=533322034 ad='2999e344'
select rownum,object_id,owner,object_name from t1 where rownum< =5000 order by object_id
END OF STMT
PARSE #3:c=40994,e=476949,p=301,cr=172,cu=0,mis=1,r=0,dep=0,og=1,tim=1208634047724244
EXEC #3:c=0,e=131,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,tim=1208634047724505
FETCH #3:c=12998,e=12276,p=29,cr=64,cu=0,mis=0,r=1,dep=0,og=1,tim=1208634047736908
FETCH #3:c=0,e=50,p=0,cr=0,cu=0,mis=0,r=2,dep=0,og=1,tim=1208634047745324
FETCH #3:c=0,e=36,p=0,cr=0,cu=0,mis=0,r=2,dep=0,og=1,tim=1208634047745861
可以看到,发生会话failover重新连接之后,又重新执行了我们测试的那个SQL。但是输出的结果表明,客户端自动跳过了前面已经得到的数据。
我们继续做下一个测试。这一次我们将SQL修改一下:
select rownum,object_id,owner,object_name from t1 where rownum< =5000 order by dbms_random.value;
然后按上面测试过程重新进行测试,但这一次,错误发生了:
ERROR:
ORA-25401: 无法继续读取
这一次为什么select不能继续,而是失败?这个测试表明,使用TAF发生FAILOVER时,SELECT的继续执行,需要保证输出的结果一致,要保证输出结果一致,一是要查询的数据,其一致性读的时间点要相同;第二是输出的顺序要相同。上面这个测试表明,如果输出的数据顺序不同,则在FAILOVER时,SELECT不能继续。
我们做另一个测试,这次执行的SQL为:
select /*+ index(t1) */ rownum,object_id,owner,object_name from t1 where object_id is not null order by object_id;
在执行SQL的同时,我们马上在另一个会话中在T1表的OBJECT_ID字段上上建一个索引,然后重启数据库:
SQL> create index test.t1_idx on test.t1(object_id);
Index created.
SQL> startup force
ORA-32004: obsolete and/or deprecated parameter(s) specified
ORACLE instance started.Total System Global Area 167772160 bytes
Fixed Size 1266392 bytes
Variable Size 100666664 bytes
Database Buffers 62914560 bytes
Redo Buffers 2924544 bytes
Database mounted.
Database opened.
执行SELECT的那个会话,错误再一次发生了:
ERROR:
ORA-08176: consistent read failure; rollback data not available
由于我们加了hint,并且在SELECT语句执行之后,在object_id上建了一个索引,这样,SELECT重新执行时,执行计划变了,由于索引是建在failover之前,SELECT语句开始执行之后,在failover发生之后,需要访问这个索引,但是访问的时间点(就是一致性读的时间点)却在建这个索引之前,所以会发生“consistent read failure”的失败。
这一次我们再执行与上面相同的SQL:
select /*+ index(t1) */ rownum,object_id,owner,object_name from t1 where object_id is not null order by object_id;
但是这一次我们在重启之前,把t1这个索引删除掉,然后再重启数据库。
按前面的结果推断,这一次应该不会再发生错误,虽然索引删除了,failover之后执行计划发生了变化,但是由于有order by ,输出的结果会是相同的。然而事实上,却再次发生了错误:
ERROR:
ORA-25401: 无法继续读取
看起来,如果SQL的执行计划发生了变化,那么failover之后,select仍然会失败。
从本文的几个测量可以表明,使用TAF,failover之后,需要满足以下几个条件,select才会正常地继续执行:
- 执行计划不发生变化
- 输出的结果顺序不发生变化
- 输出的结果集不发生变化(这通过一致性读来保证)
failover后,重新执行SELECT时,oracle是如何知道这个一致性读的时间点的?oracle是如何知道前后两次执行的执行计划是否发生了变化?有待于继续探究。但上面的测试可以得出的结论,能够让我们正确地认识failover之后,select还是有可能会失败。
期待你的下一个案例构造!
[回复]
下一部分,应该会涉及到RAC了。
[回复]
[…] 前两篇文章(TAF PartI和TAF PartII)主要描述了在TAF发生故障转移时正在执行SELECT时的行为。本文将观察当故障转移发生时如果正在执行DML和DDL语句的行为。 […]