记一个SQL优化案例

Posted by 老熊 on 2月 6th, 2009

某省电信在做批扣(批销)时,出现严重的性能问题,发现下面这一条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 »

祝新年快乐

Posted by 老熊 on 1月 24th, 2009

鼠年将去,牛年将至。祝愿我的朋友们,在新的一年里,身体健康最重要,工作顺心,家庭幸福。
过去的一年,是不平凡的一年,我们经历了很多如雪灾,地震这样让人悲痛的灾难,也经历了奥运这样让人激动和兴奋的盛会。我希望在新的一年里,我的朋友们,能够在经济危机的影响下,依然能够快乐生活每一天。

怎样修改oracle的sysdba用户组

Posted by 老熊 on 1月 10th, 2009

在安装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用户组是正确的。

我的2009年计划

Posted by 老熊 on 1月 7th, 2009

虽然说计划没有变化快,但我还是愿意列一个2009年的计划,或者说是2009年的目标。
人活着,总是离不开生活,工作和学习。学习是为了更好地工作,工作是为了生活得更好。所以我老熊的2009年计划(目标),对于工作、生活和学习都会有所涉及。

首先是关于学习了,主要是Oracle,《Cost Based Oracle Fundamentals》这本书,需要反复研究。《Troubleshooting Oracle Perforamnce》和《Oracle Database 10g Performance Tuning Tips and Techniques》这两本书,至少要看一遍,希望有所收获。Oracle的官方文档《Oracle10g Database Performance Tuning Guide And Reference》和《Oracle10g Database Administrator’s Guide》也要经常翻翻。对于RAC、Partition、Dataguard等内容,需要进一步深入。

花一点时间,更深入地研究一下Oracle内部的东西,虽然由于资料的匮乏,这个有些难度。不过,研究一个东西,是相当有意思的一件事情,不是吗?

如果有时间,涉足一下Oracle 11g的新功能。

除了Oracle,在操作系统及存储上,能够掌握更多的知识,当然主要是性能方面。

每周在这个网站上发表一篇原创的技术文章。虽然有时很忙,但个人认为,忙不是理由。写这些文章,我收获良多,并且把我所知道的东西共享出去,也是一件乐趣。所以,这是一件非常值得坚持做下去的事情。

在2008年初的时候,为了更好地研究oracle的块格式,也曾打算写一个类似于DUL的工具(我自己取名叫ODU),并最终付诸实施,只不过,个人对这个兴趣不是特别大,必竟已经有那么多同类型的工具了。不过,通过ODU这个程序的编写,掌握了Oracle的各种表,字段类型和索引的存储格式,收获还是很大的,我打算在2009年全部写完。这个话题,我会另外撰文详述。

2009年,希望能够站在更高的层面上,思考Oracle、操作系统、存储,甚至是IT系统上的整体架构。不再仅限于具体的技术细节。

谈完学习,再谈工作。根据目前的工作性质,能够保持工作热情,更有效率和更有质量地完成工作,是2009年的目标。

至于生活,我个人其实欲望并不高。只希望一家人,开开心心在一起生活,就足够了。天天陪着儿子玩一会儿,就已经感到非常满足。

一年的时间很快就会过去,2009年底的时候,我们再回头看看,上面的计划和目标到底有没有实现。计划先列在这里,也算是对我的一种鞭策,让我找到目标。

别了,2008

Posted by 老熊 on 12月 31st, 2008

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

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

别了,2008
欢迎你,2009

全表扫描与buffer cache

Posted by 老熊 on 12月 23rd, 2008

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 »

DBMS_STATS、ANALYZE以及Global Statistics

Posted by 老熊 on 12月 15th, 2008

什么是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 »

利用复合索引解决性能问题一例

Posted by 老熊 on 12月 8th, 2008

故障发生时间: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 »

TNS_ADMIN和OEM引起的血案

Posted by 老熊 on 11月 28th, 2008

一个客户的重要生产系统,一个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按键就能葬送整个系统。
  • 尽量将生产系统、测试系统与开发库隔离,比如禁止在开发机器上直接连接生产库,开发完成后,需要部署到生产库时,遵循专门的流程进行。也就是要规范开发流程。

绑定变量与函数调用

Posted by 老熊 on 11月 22nd, 2008

本文源起小荷博客中的文章“一次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 »


Copyright © 2007 老熊的三分地-Oracle、UNIX、数据恢复. All rights reserved.