某省电信在做批扣(批销)时,出现严重的性能问题,发现下面这一条SQL性能非常低下:

SELECT A.ACCT_BALANCE_ID,
       A.BALANCE_TYPE_ID,
       A.ACCT_ID,
       NVL(A.SERV_ID, -1) SERV_ID,
       NVL(A.ITEM_GROUP_ID, -1) ITEM_GROUP_ID,
       A.OBJECT_TYPE_ID,
       F.PRIORITY,
       A.BALANCE,
       NVL(A.CYCLE_UPPER, -1) CYCLE_UPPER,
       NVL(A.CYCLE_LOWER, -1) CYCLE_LOWER,
       NVL(A.CYCLE_UPPER_TYPE, ' ') CYCLE_UPPER_TYPE,
       NVL(A.CYCLE_LOWER_TYPE, ' ') CYCLE_LOWER_TYPE,
       B.ADJUST_FLAG ADJUST_FLAG,
       B.ALLOW_TRANS ALLOW_TRANS,
       B.CORPUS_FLAG,
       NVL(TO_CHAR(A.EFF_DATE, 'YYYYMMDDHH24MISS'), ' ') EFF_DATE,
       NVL(TO_CHAR(A.EXP_DATE, 'YYYYMMDDHH24MISS'), ' ') EXP_DATE,
       A.STATE,
       TO_CHAR(A.STATE_DATE, 'YYYYMMDDHH24MISS') STATE_DATE,
       B.BALANCE_TYPE_NAME,
       NVL(C.ACCT_NAME, ' ') ACCT_NAME,
       NVL(D.ACC_NBR, ' ') SERV_NAME,
       NVL(E.ITEM_GROUP_NAME, ' ') ITEM_GROUP_NAME
  FROM (SELECT ACCT_BALANCE_ID,
               BALANCE_TYPE_ID,
               ACCT_ID,
               NVL(SERV_ID, -1) SERV_ID,
               NVL(ITEM_GROUP_ID, -1) ITEM_GROUP_ID,
               OBJECT_TYPE_ID,
               BALANCE,
               NVL(CYCLE_UPPER, -1) CYCLE_UPPER,
               NVL(CYCLE_LOWER, -1) CYCLE_LOWER,
               NVL(CYCLE_UPPER_TYPE, ' ') CYCLE_UPPER_TYPE,
               NVL(CYCLE_LOWER_TYPE, ' ') CYCLE_LOWER_TYPE,
               EFF_DATE,
               EXP_DATE,
               STATE,
               STATE_DATE,
               0 SHARE_RULE_PRIORITY
          FROM ACCT_BALANCE
         WHERE ACCT_ID = :LACCTID
           AND BALANCE > 0
           AND BALANCE_TYPE_ID != 1
           AND STATE = '10A'
        UNION
        SELECT A1.ACCT_BALANCE_ID,
               A1.BALANCE_TYPE_ID,
               B1.ACCT_ID,
               NVL(B1.SERV_ID, -1) SERV_ID,
               NVL(B1.ITEM_GROUP_ID, -1) ITEM_GROUP_ID,
               A1.OBJECT_TYPE_ID,
               A1.BALANCE,
               NVL(B1.UPPER_AMOUNT, -1) CYCLE_UPPER,
               NVL(B1.LOWER_AMOUNT, -1) CYCLE_LOWER,
               NVL(A1.CYCLE_UPPER_TYPE, ' ') CYCLE_UPPER_TYPE,
               NVL(A1.CYCLE_LOWER_TYPE, ' ') CYCLE_LOWER_TYPE,
               A1.EFF_DATE,
               A1.EXP_DATE,
               A1.STATE,
               A1.STATE_DATE,
               B1.PRIORITY SHARE_RULE_PRIORITY
          FROM ACCT_BALANCE A1, BALANCE_SHARE_RULE B1
         WHERE A1.ACCT_BALANCE_ID = B1.ACCT_BALANCE_ID
           AND B1.ACCT_ID = :LACCTID
           AND A1.BALANCE > 0
           AND A1.BALANCE_TYPE_ID != 1
           AND A1.STATE = '10A'
           AND NVL(B1.EFF_DATE, SYSDATE) < = SYSDATE            AND NVL(B1.EXP_DATE, SYSDATE) >= SYSDATE) A,
       BALANCE_TYPE B,
       ACCT C,
       SERV D,
       A_BALANCE_ITEM_GROUP E,
       A_BALANCE_OBJECT_TYPE F
 WHERE A.BALANCE_TYPE_ID = B.BALANCE_TYPE_ID
   AND A.OBJECT_TYPE_ID = F.OBJECT_TYPE_ID
   AND A.ACCT_ID = C.ACCT_ID
   AND A.SERV_ID = D.SERV_ID(+)
   AND A.ITEM_GROUP_ID = E.ITEM_GROUP_ID(+)
 ORDER BY F.PRIORITY,
          B.PRIORITY,
          A.SHARE_RULE_PRIORITY ASC,
          A.EXP_DATE ASC,
          A.EFF_DATE ASC,
          A.BALANCE ASC

查看执行计划:

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------

------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                    |  Name                       | Rows  | Bytes |TempSpc| Cost  | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                             |                             |  2723G|   696T|       |  7776M|       |       |
|   1 |  SORT ORDER BY                               |                             |  2723G|   696T|  1503T|  7776M|       |       |
|*  2 |   HASH JOIN                                  |                             |  2723G|   696T|    59M| 39355 |       |       |
|*  3 |    HASH JOIN                                 |                             |   228K|    56M|       | 23918 |       |       |
|   4 |     TABLE ACCESS FULL                        | BALANCE_TYPE                |     8 |   184 |       |     7 |       |       |
|*  5 |     HASH JOIN                                |                             |   228K|    51M|       | 23907 |       |       |
|   6 |      TABLE ACCESS FULL                       | A_BALANCE_OBJECT_TYPE       |     4 |    16 |       |     7 |       |       |
|*  7 |      HASH JOIN OUTER                         |                             |   228K|    50M|    43M| 23896 |       |       |
|*  8 |       HASH JOIN OUTER                        |                             |   228K|    40M|    38M| 23199 |       |       |
|   9 |        VIEW                                  |                             |   228K|    36M|       |  2043 |       |       |
|  10 |         SORT UNIQUE                          |                             |   228K|    11M|    38M|  2043 |       |       |
|  11 |          UNION-ALL                           |                             |       |       |       |       |       |       |
|* 12 |           TABLE ACCESS BY GLOBAL INDEX ROWID | ACCT_BALANCE                |   228K|    11M|       |    50 | ROWID | ROW L |
|* 13 |            INDEX RANGE SCAN                  | IDX_ACCT_BALANCE_ACCT_ID42  |   121K|       |       |     3 |       |       |
|  14 |           NESTED LOOPS                       |                             |     1 |   146 |       |     4 |       |       |
|* 15 |            TABLE ACCESS FULL                 | BALANCE_SHARE_RULE          |     1 |   109 |       |     2 |       |       |
|* 16 |            TABLE ACCESS BY GLOBAL INDEX ROWID| ACCT_BALANCE                |     1 |    37 |       |     2 | ROWID | ROW L |
|* 17 |             INDEX UNIQUE SCAN                | PK_P_ACCT_BALANCE2          |     1 |       |       |     1 |       |       |
|  18 |        PARTITION RANGE ALL                   |                             |       |       |       |       |     1 |    63 |
|  19 |         TABLE ACCESS FULL                    | SERV                        |    12M|   258M|       | 14070 |     1 |    63 |
|  20 |       TABLE ACCESS FULL                      | A_BALANCE_ITEM_GROUP        |   244 | 11224 |       |     7 |       |       |
|  21 |    PARTITION RANGE ALL                       |                             |       |       |       |       |     1 |    63 |
|  22 |     TABLE ACCESS FULL                        | ACCT                        |    11M|   239M|       |  8505 |     1 |    63 |
------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - access("A"."ACCT_ID"="C"."ACCT_ID")
3 - access("A"."BALANCE_TYPE_ID"="B"."BALANCE_TYPE_ID")
5 - access("A"."OBJECT_TYPE_ID"="F"."OBJECT_TYPE_ID")
7 - access("A"."ITEM_GROUP_ID"="E"."ITEM_GROUP_ID"(+))
8 - access("A"."SERV_ID"="D"."SERV_ID"(+))
12 - filter("ACCT_BALANCE"."BALANCE">0 AND "ACCT_BALANCE"."BALANCE_TYPE_ID"<>1 AND "ACCT_BALANCE"."STATE"='10A')
13 - access("ACCT_BALANCE"."ACCT_ID"=TO_NUMBER(:Z))
15 - filter("B1"."ACCT_ID"=TO_NUMBER(:Z) AND NVL("B1"."EFF_DATE",SYSDATE@!)< =SYSDATE@! AND NVL("B1"."EXP_DATE",SYSDATE@!)>=SYSDATE@!)
16 - filter("A1"."BALANCE">0 AND "A1"."BALANCE_TYPE_ID"<>1 AND "A1"."STATE"='10A')
17 - access("A1"."ACCT_BALANCE_ID"="B1"."ACCT_BALANCE_ID")

Read the rest of this entry

,

在安装oracle时,oracle会提示输入sysdba用户组名,凡是以操作系统认证方式的连接,如果连接的用户在这个指定的用户组中,就可以用sysdba用户登陆。一般情况下,也是建议的sysdba用户组为dba。

有的时候,也存在这样一种情况,安装oracle软件的时候,输入了错误的sysdba用户组名(主要是由于创建oracle用户时指定到了错误的其他用户组中,现实中这种情况曾经出现过)。这样一来,就会导致不规范的安装,或者oracle后来修改回正确的用户组(dba),将导致oracle用户不能以sysdba权限连接数据库,甚至不能启动数据库)。不规范的安装,如果将安装的oracle软件tar到其他机器,会导致那个机器的oracle也出现oracle不能以sysdba连接的问题(有的人安装oracle时,如果有现成的,他就喜欢用tar的方式)。

如果指定了错误的sysdba用户组名,怎么样快捷修改而又不用重新安装oracle软件呢?既然在安装时可以指定,那么一定会有一个地方,存储了这个配置,这个存储的文件就是$ORACLE_HOME/rdbms/lib/config.c。

我们看看config.c这个文件的内容:
[oracle@xty lib]$ cat config.c

/* SS_DBA_GRP defines the UNIX group ID for sqldba adminstrative access. */
/* Refer to the Installation and User's Guide for further information. */

#define SS_DBA_GRP "dba"
#define SS_OPER_GRP "dba"

char *ss_dba_grp[] = {SS_DBA_GRP, SS_OPER_GRP};

从这个文件中,可以看到SYSDBA的用户组为"dba",而SYSOPER的用户组也为"dba"。如果我们把#define SS_DBA_GRP "dba"这一行改为#define SS_DBA_GRP "adm",然后执行下面的命令:relink all,再用oracle用户连接数据库时:

[oracle@xty ~]$ sqlplus /nolog

SQL*Plus: Release 10.2.0.3.0 - Production on Sat Jan 10 15:30:17 2009

Copyright (c) 1982, 2006, Oracle. All Rights Reserved.

SQL> connect / as sysdba
ERROR:
ORA-01031: insufficient privileges

SQL> connect / as sysoper
Connected to an idle instance

可以看到,oracle用户已经不能够用sysdba权限连接到数据库,也就更谈不上启动数据库了。但是由于sysoper的用户组没有改变,所以oracle仍然是可以用sysoper权限连接数据库并启动数据库的。(不过如果我们不使用操作系统认证,比如通过网络连接的方式用SID连接,并提供sys用户的密码,使用密码文件认证,也一样能够使用sysdba权限连接)

如果修改用户oracle的用户组为adm,则可以使用sysdba用户连接了。
[root@xty ~]# usermod -G adm oracle
oracle用户重新登陆到服务器上

[root@xty ~]# su - oracle
[oracle@xty ~]$ sqlplus "/ as sysdba"

SQL*Plus: Release 10.2.0.3.0 - Production on Sat Jan 10 15:42:47 2009

Copyright (c) 1982, 2006, Oracle. All Rights Reserved.

Connected to an idle instance.

最后,我们把config.c修改回原来的值("dba"),再relink all,再把oracle的用户组修改回dba,oracle也就恢复了原状。

在数据库服务器上,如果采用操作系统认证,而oracle用户又不能以sysdba权限连接(ORA-01031错误),在其他可能都排除后,有必要检查一下config.c,以确认sysdba用户组是正确的。

再过1小时,2008年就要过去了。
别了,2008
这一年里,发生了太多的事。
咱平民小百姓,不谈国家大事。谈谈自己的就好了。

2008年,在成都安定了下来。上班搞自己喜欢搞的ORACLE,下班回家陪陪父母、老婆和孩子。这日子,挺不错。
2009年,我会干什么呢?大家都会说计划没有变化快。看起来,我不用计划那么多事情了。唯一计划的是,为以后的发展储备更多的能量吧。

别了,2008
欢迎你,2009

QQ群里有位兄弟提出一个很有意思的问题,在一个db_cache_size为1.6G,几乎没有什么活动的数据库(版本为9.2.0.1)里面,一个简单的查询SELECT * FROM T,反复执行,这个查询的物理读始终很高。被查询的表不大,完全可以容纳在cache里面。根据直觉,频繁被读取的块,是应该cache在内存中的,不应该会有物理读,并且当时数据库没有其他的活动,cache中的块也不会被挤出去。

这个现象是违反直觉的,但注意,直觉的东西不一定不是正确的东西。那么为什么那个简单的SQL,在反复执行的情况下,怎么会有那么高的物理读呢?

这还得从Oracle的buffer cache管理说起。一个oracle进程在做全表扫描时,buffer会放置到LRU-AUX链表的尾端。如果在读块时,如果已经没有free buffer,那么进程就会查找可以被age out的块,这个查找过程是从LRU-AUX链表的尾端开始的。也就是说LRU-AUX链表尾端的buffer,总是第一个被替换的。

基于Oracle的这种LRU算法,我们假设,现在系统中已经没有free buffer,也就是buffer cache已经用完。实际上这种情况是最常见的,必竟现在的库相对于db_cache_size来说,总是大很多倍,buffer cache很快就会被填满。那么,在做全表扫描时,第1次多块读(multiblock read)的buffer会放置到LRU-AUX链表的尾端(不管这个buffer是从哪里得到的),第2次多块读时,需要buffer时,oracle也会从LRU-AUX链表的的尾端开始进行查找可以被替换的buffer,而刚好LRU-AUX链表的尾端的buffer正是上一次多块读时的buffer。也就是说前一次读到的块,很快就会被后面读的块替换掉了。

因此,不难理解,为什么在数据库活动很少时,反复扫描同一个表(这里不是同时扫描),其物理读仍然很高。并不是我们直觉的那样,这个表已经很“热”,应该全部在内存中命中。

下面我们通过一个测试来验证我们的推断。

测试环境:
Oracle 10.2.0.3 32Bit for Linux
我们设置如下的参数,以便让buffer cache的大小在我们控制的范围内:

SQL> alter system set shared_pool_size=100m;

System altered.

SQL> alter system set db_cache_size=30m;

System altered.

SQL> alter system set sga_target=200m;

Read the rest of this entry

什么是global statistics?

大家都知道,dbms_stats是Oracle 9i及后续的版本中用于收集优化器统计信息的包,虽然analyze命令也一直可用,但是现在已经不推荐使用analyze来收集统计信息,而是使用dbms_stats。二者之间一个很大的不同,也是dbms_stats一个很突出的优点就是能够正确收集分区表的统计信息,换言之就是global statistics。而analyze命令只会收集最低层次对象的统计信息,然后推导和汇总出高一级的统计信息,如分区表只会收集分区信息,然后再汇总所有分区的统计信息,得到表一级的统计信息。

那什么是global statistics?简单地说global statistics就是指直接从对象本身这一级收集到的统计信息,而不是从下一级对象“推导”或“汇总”出来的统计信息。比如,表的global statistics指直接通过表收集到的统计信息,而不是从分区收集的统计信息进行汇总或推导出的。同样,分区的global statistics是指直接通过分区收集到的统计信息,而不是从子分区收集的统计信息进行汇总或推导出的。global statistics对优化器来说是非常重要的,一个SQL,除非其查询条件限定了数据只在部分分区上,否则在大多数情况下需要global statistics才能得到正确的执行计划。

有的统计值可以从对象的下一级对象进行汇总后得到,比如表的总行数,可以通过各分区的行数相加得到。但有的统计值则不能从下一级对象得到,比如列上的唯一值数量(distinct value)以及密度值(density)。

怎样收集global statistics?

global statistics只能通过dbms_stats包来收集。注意,用analyze命令得到的统计信息,虽然也会有表一级的统计值,但是,那些值是从分区或子分区推导和汇总出来的,是不精确的。后面的实验中,将会验证这一点。

使用dbms_stats收集统计信息时,参数granularity(比如gather_table_stats过程) 指定了哪个级别上的统计信息会被收集:

  • Global -- Table (表)
  • Partition -- Partition (分区)
  • SubPartition -- SubPartition (子分区)
  • Default -- Table + Partition (表+分区)
  • All -- Table + Partition + Subpartition (表+分区+子分区)
  • Auto -- Table + Partition + Subpartition (10g,表+分区,当子分区是list分区时还包括子分区)

比如,要在一个做为子分区的表上,以'ALL'级别收集统计信息时,会收集”表+分区+子分区“上的统计信息,相当于需要执行下面的三类SQL:

  • select .... from table
  • select .... from table (partition P1) -- 在每个分区上都重复一次
  • select .... from table (subpartition SP1) -- 在每个子分区上重复一次

可以看到,dbms_stats需要比analyze命令更多的时间来进行统计信息的收集。对于有子分区的表,dbms_stats至少要多花三倍的时间。

Read the rest of this entry

故障发生时间:12月6日早上
系统环境:HP Superdome系列,128G内存,64CPU,Oracle 9.2.0.8
故障现象:CPU占用将近100%,运行队列达到60-80,应用反应速度很慢。

这是一个省级电信的核心系统。

在用户反映速度很慢后,在主机上检查发现CPU很高,将近100%,而运行队列达到了60-80。检查Oracle,发现很多的会话在等待latch free,latch#为98

SQL> select * from v$latchname where latch#=98;

    LATCH# NAME
---------- ----------------------------------------------------------------
        98 cache buffers chains

检查正在等待latch free的会话正在执行的SQL,大部分都在执行类似于下面的SQL:

SELECT SUM(cnt),
       to_char(nvl(SUM(nvl(amount, 0)) / 100, 0), 'FM9999999999990.90') amount
  FROM (select count(payment_id) cnt, SUM(amount) amount
          from payment
         where staff_id = 592965
           and CREATED_DATE >= trunc(sysdate)
           and state = 'C0C'
           and operation_type in ('5KA', '5KB', '5KC', '5KP'))

看起来这个SQL并不复杂,查看其执行计划:

Read the rest of this entry

,

一个客户的重要生产系统,一个USER被删除了,USER下所有的对象都被删除了。还好是下班时间,也有有效的备份,数据得以完全恢复,也没有对生产造成重大影响。

引起这个事故的,除了人为因素之外,ORACLE的BUG要负一半的责任。通过操作录像(客户对系统内做系统管理的所有机器的操作都作了录像,这点非常好),我们搞清楚了数据被删除的经过。

一个开发人员,通过OEM(Oracle Enterprise Manager Console)连接到数据库上,经过他确认OEM上的那个连接字符串是正确的,然后对USER做了删除操作,但很快发现,生产库的数据被删除了。操作录像也证明那个连接字符串是正确的,那么问题出在哪里呢?

操作的那台机器(Windows系统),在系统环境变量(我的电脑=>属性=>高级=>环境变量=>系统变量)中设置了TNS_ADMIN,指向了另外的目录。现在,TNS_ADMIN指向的目录(下面简称TNS_ADMIN目录)和%ORACLE_HOME%\NETWORK\ADMIN(下面简称ORACLE目录)下都有TNSNAMES.ORA这个文件。在TNS_ADMIN中,TNSNAMES.ORA有一TNSNAME,指向生产库。在ORACLE目录中,TNSNAMES.ORA中有一同样的名称的TNSNAME指向开发库。

OEM在处理TNS_ADMIN上是有问题的。OEM在启动后,在左边的数据库目录树,是从ORACLE目录的TNSNAMES.ORA中解析出来的,完全忽略了TNS_ADMIN环境变量,就算是执行”将数据库添加到树“操作,也是完全忽略了TNS_ADMIN变量,操作的是ORACLE目录中的TNSNAMES.ORA文件,显示的连接字符串信息也是从那个文件中得到的。下面是显示信息的截图:

然而,在用这个TNSNAME进行连接数据库时,却是按照TNS_ADMIN目录中的TNSNAMES.ORA文件的配置进行连接的,如果这两个TNSNAMES.ORA都有这个TNSNAME,那么不幸就发生了,本来我们期望是连接到OEM中显示的那个数据库上,结果却连接到了另一个库上。这可以是说OEM的重大BUG。

这里谈到的OEM是9i的版本,NetCA也有这个问题,但Net Manager没有这个问题。

事情虽然过去了,但是以下几件事情我们仍然值得我们牢记:

  • 有效的备份,特别是归档模式下的有效物理备份,是保证数据不会被丢失的前提。
  • 数据库用户权限的管理,需要遵循”最少权限“的原则,不可忽视。很多数据库管理人员为了方便,给Oracle用户太大的权限,甚至是DBA角色权限。这是非常危险的。
  • 有了备份仍然不够,需要做恢复测试,避免出现问题发现备份不能恢复,否则悔之晚矣。这次事故中,由于第三方的备份软件问题,导致数据恢复至少多花了三个小时的时间。要是之前有做过完整的测试,则会发现备份软件的问题。
  • 一些危险的操作,如删除用户,删除表等操作,一定要有规范的流程,确认无误后再执行。

还有以下我的一些个人观点:

  • 数据库管理时,尽量少用图形化的软件,一次DEL按键就能葬送整个系统。
  • 尽量将生产系统、测试系统与开发库隔离,比如禁止在开发机器上直接连接生产库,开发完成后,需要部署到生产库时,遵循专门的流程进行。也就是要规范开发流程。

本文源起小荷博客中的文章“一次cpu的user比例过高的调优”,文章中提到的占用CPU比较高的SQL语句,消耗的CPU比逻辑读还要高的SQL还要多。这两条SQL是:

SELECT NETWORKTYPEID FROM PROBLEM_TAB WHERE PREFIXNUM = SUBSTR(:B1 , 1, 7)
select provinceid into :b0 from PROBLEM_TAB where PREFIXNUM=substr(:b1,1,7)

我关注这个的原因是,为啥这两个SQL比较消耗CPU,比逻辑读更高的SQL消耗得还多。这里让我感兴趣的是,这里使用了函数,函数的参数使用了绑定变量。那么,有一个问题,那就是这个函数会被调用多少次?1次还是与表中所有行相同的次数?我的推测是对于结果确定性(deterministic)的函数,如果传入参数有绑定变量,那么会是被引用(如值的比较)的次数,如果传入的参数是确定的值,那么应该是1次。这个推测是不是正确的呢,让我们来做一些测试。

首先创建测试用的函数:

create or replace package pkg_test is

g_cnt number;

function f_substr(iv_str varchar2, iv_pos in number, iv_len in number)
return varchar2 deterministic;

end pkg_test;

create or replace package body pkg_test is

function f_substr(iv_str varchar2, iv_pos in number, iv_len in number)
return varchar2 deterministic is
begin
g_cnt:=g_cnt+1;
return substr(iv_str, iv_pos, iv_len);
end;
end pkg_test;

Read the rest of this entry

如果Oracle数据库hang住了,对Oracle做system dump,或做hang analyze,是研究和解决问题的有效办法,至少在提交SR时能够有更多的有用信息。如果能够连接数据库,并能够进行操作,那么用oradebug是简单快捷的办法。

但有的时候,数据库由于hang住,sqlplus不能连接时(在10g可以尝试用sqlplus -prelim连接数据库),可以使用操作系统上的调试工具来dump oracle系统状态。在记一次Oracle数据库无响应(hang住)故障的处理一文中,就曾使用dbx做systemstate dump,并发现问题所在,并最终解决了问题。下面是当时用dbx做dump的过程:

# dbx -a 446910
Waiting to attach to process 446910 …
Successfully attached to oracle.
Type ‘help’ for help.
reading symbolic information …
stopped in iosl.select at 0×9000000000c94d8 ($t2)
0×9000000000c94d8 (select+0xfffffffffff06318) e8410028 ld r2,0×28(r1)
(dbx) print ksudss(10)

Segmentation fault in slrac at 0×100083aa0 ($t2)
0×100083aa0 (slrac+0xe4) 88030000 lbz r0,0×0(r3)
(dbx) detach

从上面可以看到,使用dbx做dump的过程为:

  • 找到有异常的进程号,比如CPU非常高,HANG住的进程等。如果做系统范围的systemstate dump,可以是其他的进程。
  • dbx -a < 进程号>
  • print ksudss(10) --这里是直接调用ORACLE程序中的ksudss函数,dump level为10,就等同于在sqlplus 中用oradebug dump systemstate 10
  • detach
  • quit

在LINUX下可以使用gdb,下面是一个例子:

[oracle@xty ~]$ ps -ef | grep LOCAL
oracle 3765 3764 1 05:55 ? 00:00:00 oraclexty (DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))
oracle 3767 3668 0 05:55 pts/2 00:00:00 grep LOCAL
[oracle@xty ~]$ gdb $ORACLE_HOME/bin/oracle 3765
GNU gdb Red Hat Linux (6.1post-1.20040607.62rh)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...(no debugging symbols found)...Using host libthread_db library "/lib/tls/libthread_db.so.1".

Attaching to program: /u01/app/oracle/product/10.1.0/db_1/bin/oracle, process 3765
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libskgxp10.so...(no debugging symbols found)...done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libskgxp10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libhasgen10.so...done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libhasgen10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libskgxn2.so...done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libskgxn2.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libocr10.so...done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libocr10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libocrb10.so...done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libocrb10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libocrutl10.so...done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libocrutl10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libjox10.so...done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libjox10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libclsra10.so...done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libclsra10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libdbcfg10.so...done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libdbcfg10.so
Reading symbols from /u01/app/oracle/product/10.1.0/db_1/lib/libnnz10.so...done.
Loaded symbols for /u01/app/oracle/product/10.1.0/db_1/lib/libnnz10.so
Reading symbols from /usr/lib/libaio.so.1...done.
Loaded symbols for /usr/lib/libaio.so.1
Reading symbols from /lib/libdl.so.2...done.
Loaded symbols for /lib/libdl.so.2
Reading symbols from /lib/tls/libm.so.6...done.
Loaded symbols for /lib/tls/libm.so.6
Reading symbols from /lib/tls/libpthread.so.0...done.
[Thread debugging using libthread_db enabled]
[New Thread -1219938624 (LWP 3765)]
Loaded symbols for /lib/tls/libpthread.so.0
Reading symbols from /lib/libnsl.so.1...done.
Loaded symbols for /lib/libnsl.so.1
Reading symbols from /lib/tls/libc.so.6...done.
Loaded symbols for /lib/tls/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Reading symbols from /lib/libnss_files.so.2...done.
Loaded symbols for /lib/libnss_files.so.2
0x006967a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
(gdb) print ksudss(10)
[Switching to Thread -1219938624 (LWP 3765)]
$1 = 213658428
(gdb) detach
Detaching from program: /u01/app/oracle/product/10.1.0/db_1/bin/oracle, process 3765
(gdb) quit

然后我们可以找到有dump结果的trace文件:

Read the rest of this entry

,

我们知道,在Oracle 10g中,如果数据库实例hang住了,应用及sqlplus都不能连接时,可以用sqlplus -prelim连接数据库。那么sqlplus连接时,加上-prelim这个参数有什么特别的地方呢?下面,让我们来研究一下:

测试环境:Linux AS4上的Oracle 10.2.0.1,客户端(sqlplus)版本为Windows 2003下的10.2.0.1。

首先在数据库上启用10046事件:

SQL> alter system set events '10046 trace name context forever';

System altered.

先用普通的方式连接:

D:\>sqlplus /nolog

SQL*Plus: Release 10.2.0.1.0 - Production on 星期六 11月 15 15:36:02 2008

Copyright (c) 1982, 2005, Oracle. All rights reserved.

SQL> conn sys/manage@xty as sysdba
已连接。
SQL> exit
从 Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options 断开

再看看加prelim参数时的情况:

D:\>sqlplus -prelim /nolog

SQL*Plus: Release 10.2.0.1.0 - Production on 星期六 11月 15 15:36:34 2008

Copyright (c) 1982, 2005, Oracle. All rights reserved.

SQL> conn sys/manage@xty as sysdba
初级连接已建立
SQL> exit
从 ORACLE 断开

从上面的信息可以看到,在使用prelim连接时,提示为“初级连接已建立”,退出sqlplus没有显示banner。
也可以通过下面的方式来用prelim方式连接数据库:

[oracle@xty ~]$ sqlplus /nolog

SQL*Plus: Release 10.2.0.3.0 - Production on Tue Dec 2 07:04:28 2008

Copyright (c) 1982, 2006, Oracle. All Rights Reserved.

SQL> set _prelim on
SQL> connect / as sysdba
Prelim connection established

再看看数据库的10046 trace:

从生成的trace文件中,可以发现在正常连接时,连接上数据库后,sqlplus自动执行了下面的SQL:

ALTER SESSION SET NLS_LANGUAGE= 'SIMPLIFIED CHINESE' NLS_TERRITORY= 'CHINA' NLS_CURRENCY= '¥' NLS_ISO_CURRENCY= 'CHINA' NLS_NUMERIC_CHARACTERS= '.,' NLS_CALENDAR= 'GREGORIAN' NLS_DATE_FORMAT= 'DD-MON-RR' NLS_DATE_LANGUAGE= 'SIMPLIFIED CHINESE' NLS_SORT= 'BINARY' TIME_ZONE= '+08:00' NLS_COMP= 'BINARY' NLS_DUAL_CURRENCY= '¥' NLS_TIME_FORMAT= 'HH.MI.SSXFF AM' NLS_TIMESTAMP_FORMAT= 'DD-MON-RR HH.MI.SSXFF AM' NLS_TIME_TZ_FORMAT= 'HH.MI.SSXFF AM TZR' NLS_TIMESTAMP_TZ_FORMAT= 'DD-MON-RR HH.MI.SSXFF AM TZR'

select value$ from props$ where name = 'GLOBAL_DB_NAME'

select SYS_CONTEXT('USERENV', 'SERVER_HOST'), SYS_CONTEXT('USERENV', 'DB_UNIQUE_NAME'), SYS_CONTEXT('USERENV', 'INSTANCE_NAME'), SYS_CONTEXT('USERENV', 'SERVICE_NAME'), INSTANCE_NUMBER, STARTUP_TIME, SYS_CONTEXT('USERENV', 'DB_DOMAIN') from v$instance where INSTANCE_NAME=SYS_CONTEXT('USERENV', 'INSTANCE_NAME')

select decode(failover_method, NULL, 0 , 'BASIC', 1, 'PRECONNECT', 2 , 'PREPARSE', 4 , 0), decode(failover_type, NULL, 1 , 'NONE', 1 , 'SESSION', 2, 'SELECT', 4, 1), failover_retries, failover_delay, flags from service$ where name = :1

而sqlplus使用prelim连接上数据库后,没有生成10046 trace文件,看起来没有执行SQL,也就是没有执行任何初始化动作和查询必要的信息。也许这也就是称之为“初级连接”的来历吧。

由于使用prelim方式连接,没有执行sql语句的,所以在数据库的某些hang住的情况下,能够连接上数据库。比如由于library cache latch 被长时间持有不能释放,不能解析SQL语句引起的hang。有的人会说,我的应用刚连上去还没做任何操作就hang住了。这只是表面现象,连接上数据库后,一般都会做一些初始化的操作,如设定环境之类的。

sqlplus -prelim能够在数据库hang住的情况下连接数据库,但只能说是连接,并不代表能够做很多操作。比如执行SQL查询。这种情况下,可能最有用的就是使用oradebug。

本文只是简单地“inside”,其实并不很深入,Oracle还会有其他的跟prelim有关的东西,是我们还没发现的。有兴趣的朋友,可以进一步研究。比如通过抓取tns包进行分析等等。

,