在以前的一篇文章《DBMS_STATS、ANALYZE以及Global Statistics》中,提到使用10g数据库dbms_stats收集统计信息时,granularity缺省值为“AUTO”,其含义是“Auto -- Table + Partition + Subpartition (10g,表+分区,当子分区是list分区时还包括子分区)”。本文就这个问题再深入地探讨一下。

大家都知道,子分区有两种,一种是分区为RANGE,子分区为HASH,另一种是分区为RANGE,子分区为LIST。在10g数据库中,如果在使用dbms_stats收集统计信息时,如果没有显式指定granularity(粒度),那么granularity就会取自dbms_stats配置:
而其缺省值是“AUTO",而不再是9i下的”DEFAULT":

SQL> select dbms_stats.get_param('granularity') param from dual;

PARAM
------------------------------
AUTO

而10g自带的自动收集统计信息的任务“GATHER_STATS_JOB",其granularity同样是取自granularity param。当然可以通过下面的SQL来更改其值:

SQL> exec dbms_stats.set_param('granularity','global and partition');

这样更改后,dbms_stats默认就会收集表以及分区级统计信息,不收集子分区级统计信息。

那么,granularity=auto时,到底是怎么样的呢?前面说到了子分区是以list方式分区时,那么就会收集子分区级统计信息,其言外之意就是如果子分区是以hash方式分区时就不会收集子分区统计信息了。到底是不是这样呢?下面做个测试,测试环境是Oracle 10.2.0.4 for Linux AS4:

QL> create table t1
  2  partition by range(object_id)
  3  subpartition by hash(data_object_id)
  4  subpartitions 4
  5  ( partition p1 values less than(10000),
  6    partition p2 values less than(20000),
  7    partition p3 values less than (maxvalue)
  8  )
  9  as select * from dba_objects;  

Table created.

SQL> create table t2
  2  partition by range(object_id)
  3  subpartition by list(object_type)
  4  subpartition template(
  5    subpartition sp1 values ('TABLE'),
  6    subpartition sp2 values ('INDEX'),
  7    subpartition sp3 values ('VIEW'),  
  8    subpartition sp4 values (DEFAULT)
  9  )  
 10  ( partition p1 values less than(10000),
 11    partition p2 values less than(20000),
 12    partition p3 values less than (maxvalue)
 13  )
 14  as select * from dba_objects; 

Table created.

我们先建再从个测试表,表T1是RANGE+HASH方式的复合(组合)分区表,表T2是RANGE+LIST方式的复合分区表。
下面将"granularity" param重新设回为”auto“,然后收集T1和T2的统计信息:

Read the rest of this entry

前几天客户遇上这样一个问题,某个用户A将视图的SELECT给予另一个用户B,但是用户B查询这个视图时,仍然报错:ORA-01031: 权限不足。这是怎么一回事呢?下面来模拟一下这个过程:

有三个用户test1,test2,test3, 三个用户都具有DBA色色权限。

用TEST1用户创建一个表T1,并将其查询权限授予TEST2:

SQL> create table t1 as select * from all_objects;

表已创建。

SQL> grant select on t1 to test2;

授权成功。

用TEST2用户创建一个视图,视图的基表是TEST1.T1,并将查询权限授予TEST3:

SQL> create view v_t1 as select * from test1.t1;

视图已建立。

SQL> grant select on v_t1 to test3;

授权成功。

TEST3用户查询视图TEST2.V_T1:

SQL> select * from test2.v_t1 where rownum<1;
select * from test2.v_t1 where rownum<1
                    *
ERROR 位于第 1 行:
ORA-01031: 权限不足

可以看到报了权限不足的错误,就算这里TEST3用户有DBA权限。
这到底是怎么回事呢?
其实视图的权限,有两点需要引起注意:

1. 视图中,类似于定义者权限的存储过程,是屏蔽了角色权限的。比如如果TEST1没有显式地将T1表的SELECT权限给予TEST2,那么TEST2在创建视图V_T1时也会报ORA-01031错误,即使TEST2用户拥有DBA角色权限。

2.如果在用户A的视图中,引用了其他用户B的表,用户A将视图的访问权限给予用户C,那么就变相地将用户B的表的访问权限给予了用户C,因此,用户A必须有将用户B的表的访问权限转授用户C的权限,也就是用户B在授予A权限时,必须使用with grant option。

显然这里正是由于第2点的原因,导致用户TEST3不能访问视图。用户TEST1执行下面的操作,将解决这个问题:

SQL> grant select on t1 to test2 with grant option;

授权成功。

对于视图的UPDATE,DELETE权限,同样是如此。

在测试时,有一个现象,有点意思。就是如果用户TEST2没有显式地把V_T1的SELECT权限授予TEST3,而TEST3在有SELECT ANY TABLE或DBA权限时,则查询这个视图时不会报权限不足的错误。由于有SELECT ANY TABLE权限的存在,所有的用户表都可以被访问。但是显式授予表的权限时,似乎表的权限有更高的优先级,并且没有跟系统权限和角色权限进行结合。或者版本不同,表现得不一样,在我的测试中,是Oracle 9.2.0.8 for Windows。

,

在自动UNDO管理模式下,我们有时仍然想手动删除UNDO段。比如某个UNDO段出现了逻辑坏块。
下面首先来看看,直接删除UNDO段能不能成功。

SQL> drop rollback segment "_SYSSMU9$";
drop rollback segment "_SYSSMU9$"
*
ERROR 位于第 1 行:
ORA-30025: 不允许 DROP 段 '_SYSSMU9$' (在撤消表空间中)

看来是行不通的。那么怎么样才能删除呢?试试下面的办法:

SQL> alter session set "_smu_debug_mode"=4;

会话已更改。

SQL> drop rollback segment "_SYSSMU9$";
drop rollback segment "_SYSSMU9$"
*
ERROR 位于第 1 行:
ORA-01545: 指定的回退段'_SYSSMU9$'不可用

还是不行。下面我们看看UNDO段的状态:

SQL> select segment_name,status from dba_rollback_segs;

SEGMENT_NAME                   STATUS
------------------------------ ----------
SYSTEM                         ONLINE
_SYSSMU1$                      ONLINE
_SYSSMU2$                      ONLINE
_SYSSMU3$                      ONLINE
_SYSSMU4$                      ONLINE
_SYSSMU5$                      ONLINE
_SYSSMU6$                      ONLINE
_SYSSMU7$                      ONLINE
_SYSSMU8$                      ONLINE
_SYSSMU9$                      ONLINE
_SYSSMU11$                     OFFLINE

发现这个要删除的UNDO状态为ONLINE。下面我们将UNDO段置为OFFLINE状态,再删除:

SQL> alter rollback segment "_SYSSMU9$" offline;

回退段已变更。

SQL> drop rollback segment "_SYSSMU9$";

回退段已删除。

可以看到UNDO段已经被删除。这里首先把UNDO段OFFLINE,然后再DROP。值得注意的是,在没有修改"_smu_debug_mode"的情况下,UNDO段是不能OFFLINE的。

总结:
要在UNDO自动管理模式下删除UNDO段,需要三个步骤:

  • 执行alter session set "_smu_debug_mode"=4;
  • 执行 alter rollback segment "undo-segment-name" offline;
  • 执行 drop rollback segment "undo-segment-name" ;

一套运行在Linux下的Oracle 9.2.0.4的库,出现了大量的ORA-600[4042]错误。

ORA-00600: internal error code, arguments: [4042], [31760], [], [], [], [], [], []

关于ORA-600错误,第一个参数,也就是第一个方括号中的标识,通常可以用来定位Oracle错误发生的内部模块。(可以参考Metalink Doc ID 146580.1:What is an ORA-600 Internal Error?)如果是数字,最高位通常是指一个大的模块,而接下来的一位是小的模块。比如这里[4042],4000,最高位是4,是Transaction Layer(事务层),而次高位是0,Transaction Undo(详见Metalink Doc ID : 175982.1 ORA-600 Lookup Error Categories)。

针对这个错误,很明显是跟事务有关。在处理的时候,第一反应肯定是检查TRACE文件:

ORA-00600: internal error code, arguments: [4042], [31760], [], [], [], [], [], []
Current SQL statement for this session:
INSERT INTO XXX .....
....
----- Call Stack Trace -----
calling              call     entry                argument values in hex      
location             type     point                (? means dubious value)     
-------------------- -------- -------------------- ----------------------------
ksedmp()+269         call     ksedst()+0           0 ? 0 ? 0 ? 0 ? BFFF90A4 ?
                                                   A16D886 ?
ksfdmp()+14          call     ksedmp()+0           3 ? BFFF91B0 ? 98585B4 ?
                                                   AD58FA0 ? 3 ? A4B929C ?
kgeriv()+188         call     ksfdmp()+0           AD58FA0 ? 3 ?
kgeasi()+108         call     kgeriv()+0           AD58FA0 ? AD9AFC0 ? FCA ? 1 ?
                                                   BFFF91EC ?
ktugusc()+787        call     kgeasi()+0           AD58FA0 ? AD9AFC0 ? FCA ? 2 ?
                                                   1 ? 4 ? 7C10 ?
ktuswr()+2049        call     ktugusc()+0          BFFF9394 ? D ? 1 ? 0 ? 0 ?
                                                   0 ? 0 ? 0 ?
ktusmous_online_und  call     ktuswr()+0           D ? 0 ? 0 ? 0 ? 0 ? 1 ?
oseg()+898                                         
ktusmaus_add_us()+3  call     ktusmous_online_und  1 ? 1 ? BFFF94F8 ? 1 ?
27                            oseg()+0             
ktubnd()+7646        call     ktusmaus_add_us()+0  BFFF9CEC ? 0 ?
ktuchg()+581         call     ktubnd()+0           BFFF9678 ? 8468F4F0 ?
                                                   BFFF9CEC ? 0 ?
ktbchg2()+318        call     ktuchg()+0           2 ? 89E91A08 ? 1 ? B7BC3484 ?
                                                   B7BC348C ? AD7BECC ?
                                                   BFFF9CEC ? AD7BE38 ? 0 ? 0 ?
kdtchg()+1406        call     ktbchg2()+0          0 ? 89E91A08 ? B7BC3484 ?
                                                   B7BC348C ? AD7BECC ?
                                                   BFFF9CE4 ? AD7BE38 ? 0 ? 0 ?
kdtwrp()+2272        call     kdtchg()+0           B7BA8638 ? B7BC3484 ?
                                                   B7BC348C ? AD7BECC ?
                                                   AD7BE38 ? 1 ? 1C6 ?
kdtInsRow()+1724     call     kdtwrp()+0           B7BA8638 ? B7BA0000 ?
                                                   60DD40A4 ? B7BC31AC ? C ?
                                                   2C88E28 ?
insrow()+275         call     kdtInsRow()+0        B7BA8638 ? 89E95354 ?
                                                   89E950EC ? B7BA8418 ?
                                                   9840000 ? AD589E8 ?
insdrv()+2566        call     insrow()+0           B7BA8638 ? BFFF9FEC ? 0 ?
insexe()+1665        call     insdrv()+0           B7BA8638 ? 89E950EC ? 0 ?
                                                   B7BA8418 ? 0 ? 0 ?
opiexe()+10831       call     insexe()+0           89E95354 ? BFFFA220 ?
opipls()+6068        call     opiexe()+0           4 ? 3 ? BFFFA98C ?
opiodr()+5238        call     kjxsupd()+987        66 ? 6 ? BFFFB64C ?
rpidrus()+140        call     opiodr()+0           66 ? 6 ? BFFFB64C ? 5 ?
skgmstack()+211      call     rpidrus()+0          BFFFB028 ? 10 ? BFFFB040 ?
                                                   BFFFB3E4 ? BFFFB028 ?
                                                   899782A ?
rpidru()+93          call     skgmstack()+0        BFFFB040 ? AD5A760 ? F618 ?
                                                   899782A ? BFFFB028 ?
rpiswu2()+777        call     rpidru()+0           BFFFB3E4 ? 40 ? 40 ? 0 ?
                                                   40C ? A4B929C ?
rpidrv()+1452        call     rpiswu2()+0          837327D8 ? 40 ? BFFFB4E8 ?
                                                   2 ? BFFFB508 ? 40 ?
psddr0()+113         call     rpidrv()+0           5 ? 66 ? BFFFB64C ? 3A ?
                                                   AD5907C ? BFFFB7F8 ?
psdnal()+173         call     psddr0()+0           5 ? 66 ? BFFFB64C ? 30 ? 20 ?
                                                   B7BBB6B8 ?
pevm_EXECC()+458     call     psdnal()+0           BFFFC844 ? BFFFC834 ?
                                                   AD53500 ? B7BBB6B8 ?
                                                   856EA21C ? 856EA21C ?
pfrrun()+31877       call     pevm_EXECC()+0       B7BBF19C ? AD9E4C0 ? 20 ?
peicnt()+291         call     pfrrun()+0           B7BBF19C ? 0 ? AD9E31C ?
                                                   ADA0464 ? AD5907C ?
                                                   BFFFCC10 ?
kkxexe()+451         call     peicnt()+0           BFFFC844 ? B7BBF19C ? 2 ?
                                                   AD9954C ? 5001AA24 ? 0 ?
opiexe()+12624       call     kkxexe()+0           B7BBD068 ? B7BB022C ?
                                                   AD53504 ? B7BBD068 ? 0 ? 0 ?
opiall0()+4435       call     opiexe()+0           4 ? 3 ? BFFFD064 ?
opial7()+441         call     opiall0()+0          3E ? 22 ? BFFFD164 ?
                                                   BFFFDC0C ? BFFFD1EC ? 0 ?
opiodr()+5238        call     kjxsupd()+987        47 ? F ? BFFFDC0C ?
ttcpip()+2124        call     opiodr()+0           47 ? F ? BFFFDC0C ? 1 ?
Cannot find symbol in /lib/tls/libc.so.6.
opitsk()+1635        call     ttcpip()+0           AD53500 ? 47 ? BFFFDC0C ? 0 ?
                                                   BFFFE4E4 ? BFFFE4E0 ?
opiino()+602         call     opitsk()+0           0 ? 0 ? AD53500 ? AD8D7B8 ?
                                                   83 ? 0 ?
opiodr()+5238        call     kjxsupd()+987        3C ? 4 ? BFFFF8B0 ?
opidrv()+517         call     opiodr()+0           3C ? 4 ? BFFFF8B0 ? 0 ?
sou2o()+25           call     opidrv()+0           3C ? 4 ? BFFFF8B0 ?
main()+182           call     sou2o()+0            BFFFF894 ? 3C ? 4 ?
                                                   BFFFF8B0 ? 1 ? 0 ?
00622DE3             call     main()+0             2 ? BFFFF954 ? BFFFF960 ?
                                                   5FBC66 ? 734FF4 ? 0 ?

从SQL来看,是个简单的INSERT语句,那么就涉及到事务处理了。
从call stack来看,在stack顶端,下面的几行表明错误应该是跟回滚段有关。

# ktugusc()+787        call     kgeasi()+0           AD58FA0 ? AD9AFC0 ? FCA ? 2 ?  
#                                                    1 ? 4 ? 7C10 ?  
# ktuswr()+2049        call     ktugusc()+0          BFFF9394 ? D ? 1 ? 0 ? 0 ?  
#                                                    0 ? 0 ? 0 ?  
# ktusmous_online_und  call     ktuswr()+0           D ? 0 ? 0 ? 0 ? 0 ? 1 ?  
# oseg()+898                                           
# ktusmaus_add_us()+3  call     ktusmous_online_und  1 ? 1 ? BFFF94F8 ? 1 ?  
# 27                            oseg()+0                        

这一步的分析其实很快,基本上凭call stack中的函数名字来判断。由于当时有其他的事情在处理,同时也不在这套库的现场,就让DBA重新创建了一个UNDO表空间,并将UNDO_TABLESPACE参数设置为新的UNDO表空间名字,错误就不在出现。

其实这个故障的处理,类似于去年处理的一个案例,详见记一次并行恢复问题导致Oracle数据库Crash故障的处理

一个简单的故障处理过程,记录下来,供朋友们参考。

其实这篇文章,说讲述的跟DBA没有太大的关系,但是我写出来,希望对DBA有所帮助。

上个月一客户的某重要业务,出现2小时故障,事情闹得很大。故障发生在一个简单的INSERT语句上面,不幸的时执行那个SQL时总是报ORA-600[kcbget_24]这样的错误,其实导致事务一直失败。不过幸运的是,那个INSERT语句所要完成的业务功能只是整个业务环节中的可选功能,开发商也有开关来控制是否启动这个可选功能。虽然这个ORA-600错误,不能很快查到原因,但是却可以通过设置那个业务功能的开关来绕过这个问题。当时是我在现场处理的这个问题,从接到问题报告到解决问题,只花了很短的时间。那为什么整个故障历时了2小时。其主要原因在于,这个客户没有有效的监控手段,来监控错误,完全靠业务人员的反映,而业务人员又不能很快地发现问题。

事后检查错误出现的原因,发现是INSERT语句时,维护一个索引出现了索引,在做索引节点块分裂时报了ORA-600[kcbget_24]错误。ORA-600错误基本上是由BUG或数据损坏引起的,但BUG引起的可能性更大。那为什么之前好好的,怎么突然就出现错误了,再进一步追查发现出现问题的索引是新建的索引,而建这个索引的,是开发商的一个开发人员,未经过流程,就直接在表上建了。结果索引一建好,就出现问题了。

这里我想说的是,做DBA工作,不能有任何侥幸心理。这次发生的事故,是ORACLE的BUG引起,但是如果之前有过充分的测试,按流程来操作,很可能就分避免了这个问题的产生。对一个复杂的系统的维护,技术或许比较重要,但是我认为,流程和测试更重要。

这件事也让我想起2年前,我还在上一家单位(相对目前的工作性质来说是甲方)时,一个非常重要的业务系统,在一个非常重要的时间,崩溃了,完全不能用。这套系统本来还有一个月就功成身退,被新的业务系统所代替,结果最后一班岗也没站好,潜伏的一个致命的BUG爆发了(不是数据库软件的BUG,是业务软件的BUG)。头一天晚上系统的配置数据做了改动,按正常的流程是需要测试的,或许业务人员认为,配置的改动是常有的事,不需要测试,结果忽略了测试,结果第二天.....

工作中的某些环节,比如测试,是枯燥的,但是实在是不可或缺的。

有下面一段SQL脚本,朋友们看能不能找出问题所在?

create table t1_bak as select * from t1 order by col_1,col_2;
truncate table t1;
insert /*+ append */ into t1 select * from t1_bak;

先不看这段SQL代码的效率如何,我们来关注一下这段代码存在的严重的安全问题。

这段代码从语法上看完全没有问题,然而....
假如这段代码是用sqlplus来运行,而第1条create table语句由于空间不足,或者由于数据量太大,临时表空间不够,排序出错,那么t1_bak的创建就会失败,而紧接着,t1会被truncate掉,结果可想而知。

这不是我临时想来的问题,而是真真实实发生在现实中的。现实中发生的这件事,比我提到的隐含的问题就明显,就是第1条create table语句,存在语法问题,结果,表被truncate了,数据丢失了。

仔细考虑维护脚本,甚为重要。

与本系列的前几篇不同,本文将在RAC上测试TAF的一些特性。而测试环境又有所不同:运行于Red Hat Enterprise Linux 5上的Oracle 10.2.0.1,客户端为Windows上的10.2.0.1。在客户端的TNSNAME配置如下:

DMDB =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.81)(PORT = 1521))
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.82)(PORT = 1521))      
    )
    (CONNECT_DATA =
      (SERVICE_NAME = dmdb)
      (FAILOVER_MODE =
        (TYPE = SELECT)
        (METHOD = BASIC)
        (RETRIES = 180) (DELAY = 5)         
      )      
    )
  )

我们使用sqlplus连接到数据库中(为节省篇幅,对部分无关紧要的输出做了剪裁):

D:\>sqlplus test/test@dmdb
连接到:
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, Real Application Clusters, OLAP and Data Mining options

由于负载均衡的作用,我们需要确定会话连接的实例(节点),下面的输出给出了当前会话连接的节点和会话信息:

SQL> show parameter instance_name

NAME                                 TYPE        VALUE
------------------------------------ ----------- -------
instance_name                        string      rac2

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       147
SQL> select failover_type,failover_method,failed_over from v$session where sid=147;

FAILOVER_TYPE FAILOVER_M FAILED_OVE
------------- ---------- ----------
SELECT        BASIC      NO      

从上面输出的结果中的最后几行可以看出,会话已经启用了TAF

现在将节点2上的实例(也就是rac2)关闭,在sqlplus依次执行下面的命令,得到的结果如下:

SQL> show parameter instance_name

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------
instance_name                        string      rac1

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       146
       
SQL> select failover_type,failover_method,failed_over from v$session where sid=146;

FAILOVER_TYPE FAILOVER_M FAILED_OVE
------------- ---------- ----------       
SELECT        BASIC      YES


从结果来看,会话已经顺利地failover到了第1个节点(rac1)上

在接下来的测试中,为了避免服务器端的自动均衡对测试造成的干扰,我们将两个实例的remote_listener参数设置为空,并使用lsnrctl services命令确认设置已经生效:

SQL> alter system set remote_listener='' sid='rac1';

System altered.

SQL> alter system set remote_listener='' sid='rac2';

System altered.

同时将客户端tnsname设置中原来的两个IP地址改为一个IP地址,即将:

(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.81)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.82)(PORT = 1521))

改为:

(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.81)(PORT = 1521))

退出sqlplus,再次运行sqlplus,进行跟上面同样的测试:

Read the rest of this entry

最近有两次Oracle数据库故障与防火墙有关。这里的防火墙是硬件网络防火墙,而不是软件防火墙。

先说说简单的。一个运行在Windows系统上的Oracle 9i,客户端不能连接数据库,但是用tnsping测试没有问题。解决问题的办法很简单,但是我们仍然需要了解一下引起这个问题的原因。

这个问题首先得从客户通通过监听连接数据库的整个过程说起,此处指专用服务器连接模式:

  • 服务器上的监听进程在1521端口上进行侦听
  • 客户端发起一个数据库连接请求
  • 监听进程fork一个Oracle服务器进程(Server Process),也可称之为影子进程 (Shadow Process)。服务器进程选择一个大于1024的端口号进行侦听,监听进程把这个端口号发回到客户端,要求客户端重新连接这个指定的端口。
  • 客户端重新连接监听指定的新端口,也就是重新进行连接。
  • 客户端与Server Process直接对话,不再通过监听,进行会话认证(登录),执行SQL等等。

从上述过程可以看到,客户端最终连接的端口实际上并不是1521。由于防火墙一般只开放了几个端口,对Oracle数据库只开放了1521端口,这样在客户端进行第二次连接时,不能通过防火墙,导致连接数据库失败。

值得庆幸的是,只有Windows平台上的9i及以下版本的Oracle才会有这个问题。Oracle在Linux以及Unix平台下,多个进程间可以对端口进行复用,Oracle Server Process仍然使用的是跟监听进程一个端口(1521)。通过在linux使用strace跟踪客户端连接数据库的过程可以发现,客户端只连接了一次,并没有进行第二次连接,与上面描述的流程相比已经发生了变化。在Windows平台上,10g及以上版本的库,也同样利用端口复用,避免了这样的问题。

那么Windows上运行的Oracle 9i怎么解决这个问题呢?答案很简单,在Windows注册表的\HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\HOMEn(这里n指Oracle Home的序号,只有一个Oracle Home时是0)键下面增加一项USE_SHARED_SOCKET,其值为TRUE。然后重启监听及Oracle服务(注意要重启Oracle的服务,而不仅仅是重启数据库),就可以解决此问题。实际上10g就是默认USE_SHARED_SOCKET为TRUE。

对于这种问题,或者是让防火墙打开针对数据库主机的所有端口访问,也能解决。但是这种方案往往会被负责安全的人否决。

下面这个由防火墙导致的问题,就相对复杂一点了。

Read the rest of this entry

前两篇文章(TAF PartITAF PartII)主要描述了在TAF发生故障转移时正在执行SELECT时的行为。本文将观察当故障转移发生时如果正在执行DML和DDL语句的行为。

本文将继续使用上一篇文章TAF PartII同样的测试环境。

我们先测试当会话update时,在另一个会话立即start force实例:

SQL> update t1 set object_name=lpad('x',40,'x'),created=sysdate;
update t1 set object_name=lpad('x',40,'x'),created=sysdate
*
ERROR 位于第 1 行:
ORA-25408: 无法安全重放调用

SQL> select * from dual;

D
-
X

SQL> select failed_over from v$session where sid=(select sid from v$mystat where rownum=1);

FAI
---
YES

可以看到,在数据库实例重启完成后,会话报“ORA-25408: 无法安全重放调用”错误。为什么会报这个错。我的猜测是,由于在FAILOVER后,会话实际上是重新来执行这个SQL,但是DML语句不像SELECT一样,前者是对当前时间点的数据进行修改,保证数据修改是最新的;而SELECT的机制是保证查询的时间点一致,DML语句在重新执行时,当前时间点的数据已经跟上次执行时的数据可能不相同了,不能保证是相同的,也就是“不安全”的。

从上面的输出可以看到,在会话报错之后,仍然可以执行查询,并且从结果来看表明会话是failover后的新会话。注意,ORA-25408这个错误是在failover之后才报的,并不是数据库实例DOWN下后马上就报错。

如果我们在数据库实例级别开启sql trace,在failover后我们可以从trace文件里面可以发现,UPDATE语句并没有再次被解析和执行,而不是在UPDATE重新执行然后报错。

下面我们来看看DDL语句的情况:

Read the rest of this entry

上文,在这一节中我们来观察一下使用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修改一下:

Read the rest of this entry