其实这篇文章,我也不知道用什么标题为好。只是对今天发生的一个案例的思考。

今天是6号,是电信每个新帐期收费的第1天(可能各地有所差异)。某个省电信的收费系统Oracle数据库不堪压力,前台已经不能正常进行收费。而我登上数据库主机时,发现CPU使用率已经达到100%,90%以上的CPU使用率是user模式。这个主机有40个CPU(按逻辑CPU算是80个),而运行队列一直在130以上。

由于我之前没有接触过这套系统,对系统的能力,平时系统的状况都不是很了解。没有发现明显消耗CPU的进程,检查等待事件、以及使用statspack做了个10分钟左右的报告,没有发现特别的异常。唯一的异常是,活动会话太多。

为了解决此问题,客户立即给主机增加了16个CPU(这些CPU本来就在主机上,只是没有划给系统使用),同时重启了应用中间件。这个问题就这样暂时解决了,“火被扑灭了”。

下面分析一下今天这个问题:

大家应该都会有一个共识,应用服务器是很容易水平(横向)扩展的,一台应用服务器不够,可以再增加一台,对于很多应用,应用服务器几乎具有无限制的水平扩展能力。所以一个系统,其甁颈往往出现在数据库。RAC数据库几乎不能够线性扩展,比如两个节点的RAC,其处理能力达到单个节点库的1.6倍已经算是不错了。同时数据库往往受限于IO子系统的能力极限,扩展能力有限。因此,通过增加应用服务器,应用服务器可以接受无限制的请求,但是数据库的处理能力则是有限制的。

回到今天这个问题上,由于今天是业务高峰期,数据库接收到大量来自于应用服务器的请求,达到了某一个临界值以后,系统资源效率下降,比如,IOPS达到一定程度,IO响应时间大幅下降(当然这个系统IO子系统还没表现明显的瓶颈),CPU效率也会下降,同一个事务,以前只需要消耗CPU时间1s的,现在可能需要1.1s,一些等待也会加剧。这样应用服务器的请求队列越来越长,导致数据库的请求也越来越多,活动会话数越来越高。数据库处理的速度已经跟不上请求的速度了。CPU也就长时间保持在100%的使用率,也不足为奇了。

重启应用中间件,就强制中止了所有的请求,消除了排队,避免了给数据库增加更多的压力。重启应用中间件以及增加CPU(增加处理能力)之后,系统就恢复了正常。不过之后一段时间的观察,CPU使用率按之前40个CPU折算,也是在85%左右,也是一个比较高的值。

经过后面一段时间分析,数据库中也存在一些性能问题,存在着较大的优化余地。但是,对于这样一个成熟的庞大的系统来说,不要期望像像网上很多文章介绍的那样,优化极个别的SQL就能解决问题。存在性能不够优化的SQL很多(注意这里说的不够优化,并不是说存在严重的性能问题),并且反映出来的问题很多,涉及面相当广,比如有些表的统计信息缺失,有些SQL写法有问题,有些是索引使用不够合理,执行计划经常变动,不够稳定、有些表有碎片等等。解决这样的系统的性能问题,非一日之功。

所以今天对这个案例,让我思考的是另一方面,对于容量规划。某些系统,像电信的收费(销帐)系统,在业务高峰期,其交易量可以达到平时交易量的2-4倍(只是个粗略估计)。对于这样的系统,我们是不是应该让系统保证足够的处理能力?特别是CPU以及IO子系统能力。对于RAC数据库来说,对每个节点,是不是应该让系统足够在另一个节点DOWN掉的情况下能够支持所有的业务量,如果不能,那么RAC的高可用性就不能得到保证,一个节点DOWN掉以后,其他节点,如果不能承受DOWN掉节点转移过来的压力,也将会不堪重负而垮掉。

同时随着业务量的增加和数据量的增长,系统的压力也会越来越大。因此容量规划也需要考虑这个因素。

对于今天这样的一个系统,如果需要系统能够支撑业务高峰期的压力,通过优化系统,使之在业务低谷期,CPU的利用率应该控制在30%以下,否则今天这样的事情难免会重演。

一篇杂乱的文章,没有详细讨论容量规划,这是一个很大的话题,欢迎讨论。

与本系列的前几篇不同,本文将在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

这篇文章看起来有点标题党的感觉。

昨天一位网友管理的数据库(版本9iR2,平台Windows),由于存储阵列问题,挂了,再也起不来了。

数据库本来有RMAN做的备份,但不幸的是,备份数据与数据库放在同一台服务器,同一个硬盘上,备份文件不幸地变成了0字节。

数据库在打开时,报ORA-1578错误,错误块为系统表空间文件号1的第417块。这可是系统在自举(bootstrap)时非常重要的一个数据块,具体可以参见eygle的文章《Oracle中独一无二的Cache对象》

使用dbv检查数据文件,发现很多很多的坏块。

我让网友对现有的数据库中的所有文件(数据文件,在线日志文件等)做一个冷备份。然后使用我开发的ODU,dump 文件1的第417块,”神奇的“事情出现了,dump出来的结果显示,这个块头显示这个块的地址居然是文件号1,块号为425,相差了8个块。继续检查发现,这个块附近的连续8个块,都偏移了8个块(均是向后偏移了),而这8个块之后的8个块,又向前偏移了8个块的位置。说的更清楚的就是,这连续的16个块,前8个块和后8个块,他们在磁盘上交换了位置

My God,这个系统疯了,是存储阵列的问题?还是操作系统的。看起来阵列的问题其可能性更大。

我花了大约20几分钟的时间,修改了一下ODU程序,对copy datafile命令加上了修正数据块交错的功能。网友用copy datafile将SYSTEM表空间的数据文件复制成一个新的文件后,用dbv检查,仍然还是有很多的坏块,不过已经比之前的少很多了。不过从dbv检查的结果来看,有很多的坏块,显示的是全0字节,也就是说,这个块中的所有数据全为0。这样的块,彻底地坏了。

不过用ODU修正块的交错之后,至少能导出一部分数据字典了,有了一线希望。

网友仍然在尝试恢复中。

这是一个惨痛的教训,系统一定要做备份,备份的数据一定不要放在与系统同一台服务器上,也一定不要放在同一个硬盘或阵列上,阵列也是不可靠的。

,

最近有两次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

大家都知道Oracle 10g的dbms_stats包与Oracle 9i相比,功能增强了很多,比如增加了display_cursor这个过程,能够查看V$SQL_PLAN视图中的执行计划,如果在statistics_level参数设置为ALL,或者执行的sql使用了gather_plan_statistics hint,则在sql执行后,会在v$sql_plan_statistics_all视图中查到SQL的执行统计信息,例如逻辑读,物理读等等。这些数据对于性能诊断有着非常大的帮助。同时v$sql_plan中的执行计划,与通过EXPLAIN PLAN得到的执行计划相比,前者是oracle执行sql时真正使用的执行计划,而后者则可能不是真正的执行计划;同时有的时候,执行过的sql使用了绑定变量,而oracle在解析sql时通常会进行绑定变量窥探,这个时候我们不能使用EXPLAIN PLAN来得到那个sql的执行计划,就算得到的跟那个sql的真实的执行计划是不一样的,所以有时我们更愿意直接从v$sql_plan中得到执行计划。

但是在oracle 9i中的dbms_xplan包没有display_cursor这个过程。不过,本文根据一个开源软件SQLT中得到的一段脚本,经过修改后,能够显示v$sql_plan和v$sql_plan_statistics中的执行计划和sql的执行统计数据。点击此处下载display_cursor_9i代码

下面是使用这个代码的示例:

SQL> select /*+ sqla */ count(*) from t1 where a<13;

  COUNT(*)
----------
     40000

在另一个会话中,得到这个SQL的hash_value , child_number以及在v$sql_plan中的执行计划。

SQL> select hash_value,child_number from v$sql where sql_text like '%sqla%' and sql_text not like '%v$sql%';

HASH_VALUE CHILD_NUMBER
---------- ------------
1742773495            0

SQL> @display_cursor_9i 1742773495 0
原值  268:   s_hash_value := &1;
新值  268:   s_hash_value := 1742773495;
原值  269:   s_child_num := &2;
新值  269:   s_child_num := 0;

HASH_VALUE: 1742773495   CHILD_NUMBER: 0
---------------------------------------------------------------------------------------------
select /*+ sqla */ count(*) from t1 where a<13

Plan hash value: 3724264953

------------------------------------------------------------
| Id   | Operation           | Name |  Rows | Bytes | Cost |
------------------------------------------------------------
|    0 | SELECT STATEMENT    |      |       |       |   25 |
|    1 |  SORT AGGREGATE     |      |     1 |     3 |      |
| *  2 |   TABLE ACCESS FULL | T1   | 44444 |  133K |   25 |
------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("A"<13)

PL/SQL 过程已成功完成。

如果我们将statistics_level设置为ALL(注意:在oracle 9i中gather_plan_statistics这个hint无效),重新执行这个SQL:

SQL> alter session set statistics_level=all;

会话已更改。

SQL> select /*+ sqla */ count(*) from t1 where a<13;

  COUNT(*)
----------
     40000

在会话2中重新进行之前的查询,只不过由于参数的参数,这个SQL有两个子游标,这次执行的游标其child_number为1:

SQL> select hash_value,child_number from v$sql where sql_text like '%sqla%' and sql_text not like '%v$sql%';

HASH_VALUE CHILD_NUMBER
---------- ------------
1742773495            0
1742773495            1

SQL> @display_cursor_9i 1742773495 1
原值  268:   s_hash_value := &1;
新值  268:   s_hash_value := 1742773495;
原值  269:   s_child_num := &2;
新值  269:   s_child_num := 1;

HASH_VALUE: 1742773495   CHILD_NUMBER: 1
-------------------------------------------------------------------------------------------------------------------
select /*+ sqla */ count(*) from t1 where a<13

Plan hash value: 3724264953

----------------------------------------------------------------------------------------------------------------
| Id   | Operation          | Name | Starts | E-Rows | A-Rows | A-Time      | Buffers | OMem | 1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------
|    1 | SORT AGGREGATE     |      |      0 |      1 |      0 | 00:00:00.00 |       0 |    0 |    0 |    0 (0) |
| *  2 |  TABLE ACCESS FULL | T1   |      0 |  44444 |      0 | 00:00:00.00 |       0 |    0 |    0 |    0 (0) |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("A"<13)

PL/SQL 过程已成功完成。

不幸的是,在另一个会话中查询v$sql_plan_statistics_all的一些结果并不正确。只有在那个执行SQL的会话(就是例子中的会话1)中,才能得到正确的结果:

----------------------------------------------------------------------------------------------------------------
| Id   | Operation          | Name | Starts | E-Rows | A-Rows | A-Time      | Buffers | OMem | 1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------
|    1 | SORT AGGREGATE     |      |      1 |      1 |      1 | 00:00:00.39 |     155 |    0 |    0 |    0 (0) |
| *  2 |  TABLE ACCESS FULL | T1   |      1 |  44444 |  40000 | 00:00:00.21 |     155 |    0 |    0 |    0 (0) |
----------------------------------------------------------------------------------------------------------------

如果v$sql_plan_statistics_all有数据,则这个脚本会生成上面的第2个示例的结果,否则,会得到示例1的结果。从输出的结果来看,朋友们,是不是与dbms_xplan的输出惊人地相似啊!

在这里只是测试了最简单的SQL,实际上这个脚本对于并行,CPU成本,TEMP临时表空间使用等数据都能够显示。有兴趣的朋友可以自己试试。

前两篇文章(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

一套运行在AIX 5.3,HACMP 5.4上的双节点RAC数据库,CRS和数据库的版本均为10.2.0.3;CRSD.BIN自动重启,并产生很多的CORE DUMP。
进入$ORA_CRS_HOME/log/<nodename>/crsd目录下,查看crsd日志文件,可以看到CRSD在重启时的日志如下:

2009-03-13 13:59:49.692: [ OCRRAW][11400]proprdc: backend_ctx->prop_sctx=[0x1017ed10]
2009-03-13 13:59:49.692: [ OCRRAW][11400]proprdc: backend_ctx->prop_sltsmx=[0x0]
2009-03-13 13:59:49.692: [ OCRRAW][11400]proprdc: backend_ctx->prop_sclsctx=[0x10365848]
2009-03-13 13:59:49.692: [ OCRRAW][11400]proprdc: backend_ctx->prop_ctx_ocrctx=[0x10362940]
2009-03-13 13:59:49.692: [ OCRSRV][11400]th_snap:8:failed to take backup prop_retval=26
2009-03-13 13:59:51.480: [ CRSOCR][11240]32OCR api procr_open_key failed for key CRS.CUR.ora!wydb3!ons. OCR error code = 3 OCR error msg:
2009-03-13 13:59:51.480: [ CRSOCR][11240][PANIC]32Failed to open key: CRS.CUR.ora!wydb3!ons(File: caaocr.cpp, line: 319)

2009-03-13 14:00:06.282: [ default][1][ENTER]32
Oracle Database 10g CRS Release 10.2.0.3.0 Production Copyright 1996, 2004, Oracle. All rights reserved
2009-03-13 14:00:06.282: [ default][1]32CRS Daemon Starting
2009-03-13 14:00:06.282: [ CRSMAIN][1]32Checking the OCR device
2009-03-13 14:00:06.285: [ CRSMAIN][1]32Connecting to the CSS Daemon
2009-03-13 14:00:06.705: [ CRSD][1]32Daemon Version: 10.2.0.3.0 Active Version: 10.2.0.3.0
2009-03-13 14:00:06.705: [ CRSD][1]32Active Version and Software Version are same
2009-03-13 14:00:06.705: [ CRSMAIN][1]32Initializing OCR
2009-03-13 14:00:06.713: [ OCRRAW][1]proprioo: for disk 0 (/dev/rwbsdb_ocr1_128m), id match (1), my id set (550012785,1028247821) total id sets (1), 1st set (550012785,1028247821), 2nd set (0,0) my votes (2), total votes (2)
2009-03-13 14:00:06.809: [ CRSD][1]32ENV Logging level for Module: allcomp 0

列出目录中的所有文件可以看到,最近几天都有core dump文件。仔细查看可以发现,每个core dump文件的间隔周期为固定的8.5小时。这是一个周期性的现象。

仔细查看上面的日志,”红色字体“的部分,引起了我的注意。这个错误,表明是在做某个东西的备份的时候报了错。应该是在备份OCR。core dump产生的周期和crsd自动重启的周期,都是固定的,这跟CRS会自动周期性地备份OCR比较吻合。

在METALINK上一番搜索,找到了BUG 5893629与这个故障现象比较符合。METALINK对这个BUG的描述称,CRS在备份OCR时,会在$ORA_CRS_HOME/cdata/<cluster_name>目录下生成一个temp.ocr,如果这个文件之前已经存在,会试图删除这个文件,而如果这个文件由于不是root用户所有,则就会导致crsd crash。这个BUG要在10.2.0.4才会修复。

根据对这个BUG的描述,进入到$ORA_CRS_HOME/cdata目录,这个目录下面有两个目录,一个是“crs”,另一个是"localhost“,一般来说,crs就是CRS集群的名称。进入这个目录,没有发现任何文件,看权限,root用户也可以建立文件。

问题在哪里呢?换一个角度思考,既然BUG描述中称不能删除旧的temp.ocr文件会导致crsd crash,那要是没有$ORA_CRS_HOME/cdata/<cluster_name>这个目录会怎么样,一样不能建立temp.ocr这个文件,那这种情况也会不会crash?这里$ORA_CRS_HOME/cdata目录下只有crs和localhost目录,如果按这种推理,如果cluster_name不是通常的”crs“呢?

不过当时有其他事情,离开了现场,不能得到CRS集群名字。不过幸运的是,我在日志文件中,在上面贴出的那段信息的前面,经过了一大段一大段的无用的信息之后,找到了下面的日志:

2009-03-13 13:59:49.391: [ OCRRAW][11400]rbkp:1: could not create the temp backup file [/oracle/crs/cdata/wydb_crs/temp.ocr]
2009-03-13 13:59:49.391: [ OCRRAW][11400]proprseterror: Error in accessing physical storage [26] Marking context invalid.

这里的日志就更明显了,就是不能创建”/oracle/crs/cdata/wydb_crs/temp.oc“这个文件。而前面的推断却不幸言中,CRS集群名字真的不是默认的"crs”,/oracle/crs/cdata这个目录下也的确没有wydb_crs这个目录。

让数据库维护人员在/oracle/crs/cdata目录中新建一个目录wydb_crs,经过一段时间的观察,故障不再出现,问题解决。

从这个故障中,我们得到一个教训,CRS有时真的很傻,为什么在备份时像这种不能创建文件的错误,不是写个日志忽略过去而是直接将进程crash了,并且还产生了core dump?为什么在安装CRS时,安装软件不自动建好这个目录,而只是按默认的"crs"名字建立目录?看来安装ORACLE软件的时候,CRS集群名字取默认值就行了。严重怀疑那个BUG是不是真的会解决这个问题(根据METALINK的描述,在备份时会随机生成一个文件名,而不再是固定的temp.ocr,但如果像这个案例中的故障,仍然会得不到解决。)

TAF,透明应用故障转移(Transparent Application Failover),是Oracle数据库提供的一项高可用特性,普遍应用于RAC环境中,当然也可以用于Data Guard和传统的HA实现的主从热备的环境中。

TAF中的Transparent和Failover,点出了这个高可用特性的两大特点:

  • TAF是用于故障转移的,也就是切换。当Oracle连接的会话由于数据库发生故障不可用时,会话能够自动切换到RAC中的其他可用的节点上,或者切换到Standby上面,或者切换到HA方式中的另一个可用的节点上面。
  • TAF的故障转移,对应用来说是透明的,应用系统不需要进行特别的处理就能够自动进行故障转移。

但是,TAF是完美的吗?是不是使用了TAF,应用就能真的无缝地进行切换呢?对应用和数据库有没有其他什么要求?要回答这些问题,我们需要全面地了解、掌握TAF。我始终认为,要用好一个东西,首先得掌握这个东西背后的工作原理与机制。

首先来看看Failover。Failover有两种,一种是连接时Failover,另一种则是运行时Failover。前者的作用在于,应用(客户端)在连接数据库时,如果由于网络、实例故障等原因,连接不上时,能够连接数据库中的其他实例。后者的作用在于,对于一个已经在工作的会话(也就是连接已经建立),如果这个会话的实例异常中止等,应用(客户端)能够连接到数据库的其他实例(或备用库)。

首先,TAF是ORACLE客户端提供的一项特性。使用TAF,对客户端的环境有一定的要求。比如JAVA的JDBC驱动、Oracle客户端的版本等(8i开始支持TAF)。这个问题将在本系统文章的后面部分详细描述。

下面看一个有趣的例子:

Read the rest of this entry

Oracle数据文件的坏块,可分为物理坏块和逻辑坏块。物理坏块(也可以称为介质坏块)指的是块格式本身是坏的,块内的数据没有任何意义。而逻辑坏块,指的是块内的数据在逻辑是存在问题。比如说索引块的索引值没有按从小到大排列。物理坏块一般是由于内存问题、OS问题、IO子系统问题和硬件引起,逻辑坏块一般是是由于Oracle Bug等原因引起。

Oracle数据文件的每个块,其块头为20字节。其定义如下:(来自于DSI401)

struct kcbh
{
    ub1 type_kcbh; /* block type */
    ub2 frmt_kcbh; 
    ub1 spare1_kcbh;
    ub1 spare2_kcbh;
    krdba rdba_kcbh; /* relative DBA */
    ub4 bas_kcbh; /* base of SCN */
    ub2 wrp_kcbh; /* wrap of SCN */
    ub1 seq_kcbh; /* sequence # of changes at the same scn */
    ub1 flg_kcbh; 
    ub2 chkval_kcbh;
};  
  

在块头中,seq_kcbh(占用1字节,块头偏移14)有着特殊的含义,如果该值为0xff,则表示该块被标记为corruption

下面我们做一个测试:

SQL> create table test.t1 as select * from dba_objects;

表已创建。

SQL> select header_file,header_block from dba_segments where segment_name='T1' and owner='TEST';

HEADER_FILE HEADER_BLOCK
----------- ------------
         10         1445

修改db_block_checksum参数值为TRUE,关闭数据库,我们用ultraedit修改10号文件的1447块的check sum(一个随便>0的数)及flag=0x04。然后再打开数据库。再执行下面的查询:

SQL> select count(*) from test.t1;
select count(*) from test.t1
*
ERROR 位于第 1 行:
ORA-01578: ORACLE 数据块损坏(文件号10,块号1447)
ORA-01110: 数据文件 10: 'D:\ORACLE\ORADATA\XJ\TEST01.DBF'

由于非系统表空间在db_block_checksum参数设为FALSE时,会忽略checksum的检查。所以这里为了测试的方便设置为TRUE。
从上面的错误信息来看,块号1447这个块已经坏了,报的错误是经典的ORA-01578错误。

Read the rest of this entry

,