本文将以ODU 3.0.2版的配置文件为例,详细介绍ODU的配置参数,从配置文件也可以体会到ODU的众多功能特点。

ODU默认的配置文件是config.txt,在启动ODU时,会自动打开这个配置文件,当然在进入ODU后,仍然可以通过"load config [config filename]"命令来装入配置文件,这个命令中的config filename(配置文件名)是可选的,如果省略此项,将载入默认的配置文件config.txt。

配置文件是一个纯文本文件,每行为一个配置参数,为“参数名”和“值”,二者均不区分大小写,之间以若干空格分隔。下面是一个配置文件的内容示例:

byte_order   little
block_size    8192
data_path   data
lob_path    /lob
charset_name ZHS16GBK
ncharset_name AL16UTF16
output_format text
lob_storage   infile
clob_byte_order  little
delimiter  |
unload_deleted no

下面将详细介绍每一项配置参数的用法以及作用:

1. BYTE_ORDER

这个参数,指示数据文件平台字节序。可选值为"LITTLE"和"BIG",默认值为“LITTLE”。

这个参数跟ODU运行在哪个平台无关,而只与数据文件的平台有关。比如,数据文件是AIX平台上的,那么这个值就是“BIG",而数据文件是x86平台上的,那么这个值就是”LITTLE“。ODU能够跨平台恢复数据,比如可以将AIX上的Oracle数据文件复制到Windows上,由Windows版本上的ODU进行恢复,反之亦然。只要通过BYTE_ORDER这个参数正确地指示数据文件的平台,即可实现跨平台恢复。

2. BLOCK_SIZE

这个参数设置数据文件缺省的块大小。ODU支持同一数据库具有不同块大小数据文件,如果在ODU的control文件中没有没文件指定块大小(参见《ODU命令详解 PartI》中“open”命令),则使用配置文件中的BLOCK_SIZE指定的大小。这个参数可选的值有2048、4096、8192、16384、32768,默认值是8192。

3. DATA_PATH

DATA_PATH指定恢复的数据所存储的目录,如果需要恢复的数量量非常大,可以用这个参数值指定一个与ODU软件所在目录不同的路径。注意这个参数指定的目录必须是已经存在的,ODU不会自动创建这个目录。
可以使用相对路径,也可以使用绝对路径。默认值为”data“,表示恢复的数据默认放在ODU软件所在目录的data子目录中。

4. LOB_PATH

Read the rest of this entry

,

这次的案例同样是一个省电信的数据库,只不过比《记一个SQL优化案例》中提到的数据库规模要大得多。先简单地介绍一下环境,运行在AIX 5300 TL05上的Oracle 9.2.0.8。系统维护人员发现一个应用的中间件队列全部堵塞。检查数据库的等待事件,发现这个应用连接的会话,基本上都是在等待latch free,latch#为98,很明显是SQL性能出现了问题。因此,检查几个会话正在运行的SQL,都是下面类似的SQL:

select c.acct_id,
       a.serv_id,
       d.cust_code,
       d.cust_id,
       a.acc_nbr,
       c.acct_name,
       c.acct_nbr_97,
       e.name serv_acct_state,
       to_char(b.state_date, 'yyyymmdd') state_date,
       f.name serv_state,
       h.product_name
  from serv a,
       serv_acct b,
       acct c,
       cust d,
       (select domain, name
          from v_domain
         where table_name = 'SERV_ACCT'
           and field_name = 'STATE') e,
       (select domain, name
          from v_domain
         where table_name = 'SERV'
           and field_name = 'STATE') f,
       product h
 where a.serv_id = b.serv_id
   and b.acct_id = c.acct_id
   and a.cust_id = d.cust_id
   and b.state = e.domain
   and a.state = f.domain
   and a.product_id = h.product_id
   and b.state = '10A'
   and c.state = '10A'
   and a.state in ('2HA', '2HC', '2HD', '2HE', '2HH', '2HN', '2HS')
   and a.serv_id in
       (SELECT distinct serv_id
          FROM serv_attr
         WHERE attr_val = '0xx833xxxxx'
           AND attr_id IN (SELECT attr_id
                             FROM a_query_acct_attr
                            WHERE state = 'A0A'
                              and attr_type = 'ACT'))

我隐去了代码中那ATTR_VAL=条件后真实的值,以'0xx833xxxxx'代替。

这个SQL咋一看跟《记一个SQL优化案例》提到的SQL都很相似,想想也能明白,都是一家开发商开发的系统^_^。

我在这条SQL中看到下面这样的代码,我就头痛:

SELECT distinct serv_id
          FROM serv_attr
         WHERE attr_val = '0xx833xxxxx'
           AND attr_id IN (SELECT attr_id
                             FROM a_query_acct_attr
                            WHERE state = ’A0A’
                              and attr_type = ’ACT’)

对于SERV_ATTR这个表,我们可理解为这个表存储了所有用户的属性,每一个用户有多行,每一行有一个ATTR_ID,表示属性ID,也就是表示是什么属性,而ATTR_VAL则是属性的值。这样可以很方便地进行扩展,比如增加属性类型,甚至是自定义属性等。但是这样的设计,虽然具有了很大的灵活性,但不得不说,这样的设计,放在数据库中,基本上违背了关系型数据库的初衷。ATTR_VAL虽然定义为VARCHAR2类型,但实际存储的数据也可能是数值,日期,只不过都以字符串来表示。这样的表,对于Oracle的优化器来说,可以说是一个巨大的挑战。

虽然如此,不得不先接受现实,得想办法让这个SQL正常。第一步当然还是看执行计划了:

Read the rest of this entry

,

接上文《Oracle压缩表数据块格式解析 PartI》,本文将分析压缩表块中实际的行数据部分。

首先我们还是先来看看行数据部分(tab 1)序号为0的行的数据:

tab 1, row 0, @0x1855
tl: 52 fb: --H-FL-- lb: 0x0  cc: 13
col  0: [ 3]  53 59 53
col  1: *NULL*
col  2: [ 5]  56 41 4c 49 44
col  3: [ 1]  4e
col  4: [ 1]  4e
col  5: [ 1]  4e
col  6: [ 2]  c1 62
col  7: [ 5]  54 41 42 4c 45
col  8: [ 7]  78 6d 03 0f 12 2a 38
col  9: [ 7]  78 6d 03 0f 12 2a 38
col 10: [19]  32 30 30 39 2d 30 33 2d 31 35 3a 31 37 3a 34 31 3a 35 35
col 11: [ 7]  41 43 43 45 53 53 24
col 12: [ 2]  c1 62
bindmp: 2c 00 0d 06 76 c9 78 79 79 79 ca c1 62 cd 54 41 42 4c 45 77 77 db 32 30 30 39 2d 30 33 2d 31 35 3a 31 37 3a 34 31 3a 35 35 cf 41 43 43 45 53 53 24 ca c1 62

bindmp那一行,与符号表中的一样,都是表示这一行数据在数据块中的原始数据,也就是压缩后的数据。我们需要把这个数据解压还原。

bindmp: 2c 00 0d 06 76 c9 78 79 79 79 ca c1 62 cd 54 41 42 4c 45 77 77 db 32 30 30 39
        2d 30 33 2d 31 35 3a 31 37 3a 34 31 3a 35 35 cf 41 43 43 45 53 53 24 ca c1 62

为了便于排版,这里把数据处理后显示为2行。

2c 行标志字节,与普通表数据块一样
00 锁标志,即ITL插槽位置
0d 这个字节表示数据被压缩后的列数,可以理解为后面的数据被分割成了几部分。
06 表示这一行中前面6列数据一定是被压缩的,具体含义在后面可以进一步解析。这一个字节,是网上很多讲压缩表格式时都没有正确地给出其意义的。
接下来的部分,与符号表类型,都是
    标志字节+数据
76 表示数据来自符号表中的序号为118(0x76)的行。
c9 依照解析符号表时的推论,应该表示接紧着后面的数据是实际的列数据,长度为1。但实际上在这里更为复杂。这里也是网上所有关于压缩表块格式分析的文章没有提到的。上面提到过”06 表示这一行中前面6列数据一定是被压缩的“,由于到这列为止,只压缩了1列,因此还没有到6列,那么这1列也是被压缩的,只不过压缩的数据没有在符号表中,而在这个字节紧接着的后面。

我们来看看上面提到的"C9”究竟代表什么?这里就不得不引入了压缩块头部中的fcls_9ir2:

Read the rest of this entry

,

前段时间花了点时间研究了一下Oracle压缩表的数据块格式,并给ODU增加了压缩表支持功能。鉴于目前在网络上基本上没有完整的关于Oracle压缩表数据块的格式分析,我觉得有必要把自己的研究得到的知识,发布出来与大家分享。

由于Oracle 9i与Oracle 10g在压缩块格式上没啥区别,我使用ODU也能够在11g上导出压缩表,所以本文就是Windows上的Oracle 9.2.0.8下的压缩表来进行块格式分析。下面我们用DBA_OBJECTS的数据建一个测试表:

SQL> create table t1 as select * from dba_objects;

表已创建。
SQL> select header_file,header_block from dba_segments where owner=user and segment_name='T1';

HEADER_FILE HEADER_BLOCK
----------- ------------
          8          137
SQL> alter system dump datafile 8 block 138;

系统已更改。

由于T1表所在的表空间是MSSM管理方式的表空间,一般来说紧接着段头后面的数据块就是存储表实际数据的第1个块。所以dump出了datafile 8的138块。

下面从块头往下一直进行解析。

Start dump data blocks tsn: 8 file#: 8 minblk 138 maxblk 138
buffer tsn: 8 rdba: 0x0200008a (8/138)
scn: 0x0000.003d0735 seq: 0x02 flg: 0x04 tail: 0x07350602
frmt: 0x02 chkval: 0xe80a type: 0x06=trans data
Block header dump: 0x0200008a
Object id on Block? Y
seg/obj: 0x1be4 csc: 0x00.3d0735 itc: 3 flg: - typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0xffff.000.00000000  0x00000000.0000.00  C---    0  scn 0x0000.003d0735
0x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000
0x03   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

这一部分是Oracle块的头部,与其他非压缩表块的头部没什么不同。接下来是表类型(相对于索引类型的数据块)的数据头部:

data_block_dump,data header at 0x3393074
===============
tsiz: 0x1f88
hsiz: 0x280
pbl: 0x03393074
bdba: 0x0200008a
     76543210
flag=-0------  这里“O”标记表示是压缩的块
ntab=2     这里说明块里面有两个“表”的数据,实际上是“符号表”和“实际的表数据”,分别是"tab 0"和"tab 1"
nrow=291
frre=-1
fsbo=0x280
fseo=0x28c
avsp=0xc
tosp=0xc
  r0_9ir2=0x0        这里及下面的数行都是压缩表特有的数据
  mec_kdbh9ir2=0x3  这个字段后面有详细说明
  r1_9ir2=0x0
            76543210
  flag_9ir2=------OC
    fcls_9ir2[7]={ 0 32768 32768 32768 32768 32768 32768 }  这个字段后面有详细说明
    perm_9ir2[13]={ 0 11 1 12 6 7 8 9 10 2 3 4 5 }  这个字段后面有详细说明
0x32:pti[0] nrow=122 offs=0
0x36:pti[1] nrow=169 offs=122
0x3a:pri[0] offs=0x189f
0x3c:pri[1] offs=0x1890
0x3e:pri[2] offs=0x1889
0x40:pri[3] offs=0x18ae
0x42:pri[4] offs=0x18c4
0x44:pri[5] offs=0x18ce

行目录数据比较多,在此大部分省略。接下来,就是tab 0,也就是符号表的数据了:

Read the rest of this entry

,

今天是5月12日,去年今日,因汶川大地震,举国悲伤。

我们希望不要沉浸在悲伤的过去,只是希望,逝者安息,生者更坚强,去创造美好的明天。祝愿灾区的小朋友能够拥有美好的未来;希望为灾区无私奉献的志愿者们,以及参与援建灾区的人们,好人一生平安。

虽然10g及以上版本的Oracle数据库,提供了recyclebin(回收站)功能,可以找回被drop的表。但是仍然存在着很多8i、9i的库以及没有开启recyclebin功能、drop时直接purge操作等,这样的情况下,如果想找回被意外drop的表,常规的手段是通过备份来恢复。如果没有备份,那就没有办法来恢复了。不过ODU提供了一个可能,在没有备份的情况下,恢复被drop表的数据。

下面通过一个示例来演示如何使用ODU来恢复被drop的表。
首先创建一个测试表:

SQL> create table odu_test ( a number,b varchar2(10),c nvarchar2(30),d varchar2(20),e date,f timestamp,g binary_float,h binary_double);

Table created.

SQL> insert into odu_test select rownum,lpad('x',10),'NC测试' || rownum, 'ZHS测试'|| rownum,sysdate+dbms_random.value(0,100),systimestamp+dbms_random.value(0,100),rownum+dbms_random.value(0,10000),rownum+dbms_random.value(0,10000) from dba_objects where rownum<=10000;

10000 rows created.

SQL> commit;

Commit complete.

SQL> create table t1 as select * from odu_test;

Table created.
SQL> drop table odu_test purge;

Table dropped.

在发现重要的表被意外drop掉的时候,应该立即停止应用,offline那个表所在的表空间或关闭数据库。这里odu_test表是建在users表空间下,先将users表空间offline:

SQL> alter tablespace users offline;

Tablespace altered.

然后需要使用logminer来查找被drop表的data object id:

SQL> select group#,status from v$log;

    GROUP# STATUS
---------- ----------------
         1 INACTIVE
         2 INACTIVE
         3 CURRENT

SQL> col member for a50
SQL> select member from v$logfile where group#=3;

MEMBER
--------------------------------------------------
/u01/app/oracle/oradata/xty/redo03.log

SQL> exec sys.dbms_logmnr.add_logfile(logfilename=>'/u01/app/oracle/oradata/xty/redo03.log');

PL/SQL procedure successfully completed.

SQL> exec sys.dbms_logmnr.start_logmnr(options=>sys.dbms_logmnr.dict_from_online_catalog);

PL/SQL procedure successfully completed.
SQL> select scn,timestamp,sql_redo from v$logmnr_contents where operation='DDL' and sql_redo like '%odu_test%' order by 2 ;

SCN TIMESTAMP SQL_REDO
---------- ------------------- ----------------------------------------------------------------------
    681455 2009-05-08 11:20:50 create table odu_test ( a number,b varchar2(10),c nvarchar2(30),d varc
                               har2(20),e date,f timestamp,g binary_float,h binary_double);

    681521 2009-05-08 11:21:17 create table t1 as select * from odu_test;
    681567 2009-05-08 11:21:34 drop table odu_test purge;

SQL> select scn,timestamp,sql_redo from v$logmnr_contents where timestamp=to_date('2009-05-08 11:21:34','yyyy-mm-dd hh24:mi:ss') order by 1;

       SCN  SQL_REDO
----------  ----------------------------------------------------------------------
    681566  set transaction read write;
    681567  drop table odu_test purge;
    681569  Unsupported
    681570  Unsupported
    681570 
    681570 
    681570 
    681570  Unsupported
    681570 
    681570 
    681570 
    681570  Unsupported
    681570 
    681570 
    681570 
    681570  Unsupported
    681570 
    681570 
    681570 
    681570 
    681570 
    681570 
    681570  Unsupported
    681570  Unsupported
    681570 
    681570 
    681570 
    681570  Unsupported
    681570 
    681570 
    681570 
    681570  Unsupported
    681570 
    681570 
    681570 
    681571  Unsupported
    681572 
    681572  delete from "SYS"."OBJ$" where "OBJ#" = '52230' and "DATAOBJ#" = '5223
            0' and "OWNER#" = '57' and "NAME" = 'ODU_TEST' and "NAMESPACE" = '1' a
            nd "SUBNAME" IS NULL and "TYPE#" = '2' and "CTIME" = TO_DATE('2009-05-
            08 11:20:46', 'yyyy-mm-dd hh24:mi:ss') and "MTIME" = TO_DATE('2009-05-
            08 11:20:46', 'yyyy-mm-dd hh24:mi:ss') and "STIME" = TO_DATE('2009-05-
            08 11:20:46', 'yyyy-mm-dd hh24:mi:ss') and "STATUS" = '1' and "REMOTEO
            WNER" IS NULL and "LINKNAME" IS NULL and "FLAGS" = '0' and "OID$" IS N
            ULL and "SPARE1" = '6' and "SPARE2" = '1' and "SPARE3" IS NULL and "SP
            ARE4" IS NULL and "SPARE5" IS NULL and "SPARE6" IS NULL and ROWID = 'A
            AAAASAABAAAMzdAAS';

    681572 
    681573  commit;
    681574  set transaction read write;
    681574  Unsupported
    681576  commit;
    681577  set transaction read write;
    681579  Unsupported
    681581  commit;

SQL> exec sys.dbms_logmnr.end_logmnr;

PL/SQL procedure successfully completed.

从SCN为681572的几行中,delete from ”SYS”.”OBJ$” where ”OBJ#” = ’52230′ and ”DATAOBJ#” = ’52230′ 可以看到被drop表的data object id为52230。

下面我们使用ODU来恢复这个被删除的表:

Read the rest of this entry

,

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

今天是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