一个电信运营商客户的核心交易系统,临时表空间大量被占用,临时表空间被撑到了600GB。这样的问题复杂吗?取决于很多因素,不过今天所要讲的案例,并不复杂,如果我们对临时表空间在何种情况下使用有足够了解。

首先,我们要去检查是什么会话占用了临时表空间,具体占用了多少,临时段的具体类型是什么。正如我们要想知道这个月的花费过大,去分析原因时就要去看是哪些开销过大、开销了多少金额、开销的用途等。

这个步骤比较简单,查询v$sort_usage就可以了:

select * from 
(select username,session_addr,sql_id,contents,segtype,blocks*8/1024/1024 gb 
from v$sort_usage order by blocks desc) 
where rownum<=200;

USERNAME    SESSION_ADDR     SQL_ID        CONTENTS  SEGTYPE            GB
----------  ---------------- ------------- --------- --------- -----------
XXXX        0700002949BCD8A0 291nk7db4bwdh TEMPORARY SORT      .9677734375
XXXX        070000294BD99628 291nk7db4bwdh TEMPORARY SORT      .9677734375
XXXX        070000294CD10480 291nk7db4bwdh TEMPORARY SORT      .9677734375
XXXX        070000294DD1AC88 291nk7db4bwdh TEMPORARY SORT      .9677734375
XXXX        070000294CD68D70 291nk7db4bwdh TEMPORARY SORT      .9677734375
XXXX        070000294DBDF760 291nk7db4bwdh TEMPORARY SORT      .9677734375
XXXX        070000294EDB5D10 291nk7db4bwdh TEMPORARY SORT      .9677734375
XXXX        070000294FD7D818 291nk7db4bwdh TEMPORARY SORT      .9677734375
...结果较多,忽略部分输出...

SQL_ID都是一样的,那这个SQL是否有其特殊性呢?SEGTYPE为SORT表明这个临时段是“排序段”,用于SQL排序,大小居然也是一样,会话占用的临时段大小将近1GB,几百个会话加在一起,想不让临时表空间不撑大都难。

看看这个相同的SQL ID代表的SQL是什么:

SQL> @sqlbyid 291nk7db4bwdh

SQL_FULLTEXT
--------------------------------------------------------------------------------------------------------------
 SELECT  A.LLEVEL,  A.LMODE  FROM TABLE_XXX A  WHERE A.SERVICE_NAME = :SERVICE_NAME AND STATE='Y'

很明显,这是一条非常简单的SQL,没有ORDER BY ,也没有GROUP BY、UNION、DISTINCT等需要排序的,TABLE_XXX是一张普通的表,而不是视图。出现了什么问题?会不会是v$sort_usage的SQL_ID列有错误?我们查看其中一个会话正在执行的SQL:

select sid,prev_sql_id, sql_id from v$session where saddr='070000294AC0D050';

        SID PREV_SQL_ID   SQL_ID
----------- ------------- -------------
       3163 291nk7db4bwdh

v$sort_usage中看到某个会话当前没有执行任何SQL,v$sort_usage中的SQL_ID是该会话前一条执行的SQL。为什么这里显示的是会话前一条执行的SQL,关于这个问题后面再详述,但至少有一点是可以判断的:如果大量的临时段都是由会话当前正在执行的SQL所产生的,那说明同时有几百个会话在执行需要大量临时空间的SQL,那系统早就崩溃了。所以这些临时表空间的占用不应该是由当前在执行的SQL所产生的,至少大部分不是。

大部分人的一个错误观点是,临时表空间中当前占用的空间是由会话当前正在执行的SQL所产生的。上面的一个简单的分析判断,情况不应该是这样。我们可以基于查询类SQL的执行过程来分析:

  1. 解析SQL语句(Parse),生成一个游标(Open Cursor)。
  2. 执行SQL语句(Execute),严格说就是执行新产生的游标。
  3. 在游标中取数据(Fetch)。
  4. 关闭游标(Close Cursor)。

关键在第3步。大家都知道取数据有一个array size的概念,表示一次从游标中取多少条数据,这是一个循环的过程。如果SQL查询得到的数据有1000条,每次取100条,则需要取10次。对于Fetch Cursor,有两点:

  1. 一个游标,或者说一条SQL语句,并不要求客户端把所有数据取完,只取了一部分数据就关闭游标也是可以的。
  2. 只要还没有关闭游标,数据库就要维护该游标的状态,如果是排序的SQL,也需要维持该SQL已经排好序的数据。

很显然,从上述第2点可以知道,如果一条SQL使用了临时段来排序,在SQL对应的游标没关闭的情况下,Oracle数据库不会去释放临时段,因为对于Oracle数据库来说,它不会知道客户端是否还要继续取游标的数据。

基于这样的分析,我们只需要随便选择一个占用了接近1GB的会话,查询v$open_cursor,查看其打开的游标中是否有大数据量排序的SQL:

SQL> select sql_id,sorts,rows_processed/executions from v$sql
  2  where parsing_schema_name='ACCT' and executions>0 and sorts>0
  3  and sql_id in (select sql_id from v$open_cursor where sid=4505)
  4  order by 3;
  
  SQL_ID              SORTS ROWS_PROCESSED/EXECUTIONS
------------- ----------- -------------------------
...省略部分输出结果...
86vp997jbz7s6       63283                       593
cfpdpb526ad43         592               35859.79899
cfpdpb526ad43         188               55893.61702
cfpdpb526ad43         443                     71000

Read the rest of this entry

如同我在《我们应该怎样安装Oracle数据库?》这一篇文章提及,只要安装Oracle软件的时候严格按文档操作,通常不会遇到太大的问题。但是,现实环境总是那么复杂,在一些新的软件组合或者是新的版本上进行安装,容易遇到一些比较复杂的或者说是解决起来不是那么容易的问题。本文将要描述的就是在这样一个环境下的安装:HP-UX 11.31 IA64、Symantec SFRAC、Oracle 10g RAC。当安装CRS运行root.sh时,一直挂起,也就是说,root.sh一直过不去。下面列出这个问题的解决过程,供朋友们参考。

先来看看运行root.sh得到输出:

cwxndb01:[/oracle/app/oracle/product/crs]#./root.sh
WARNING: directory '/oracle/app/oracle/product' is not owned by root
WARNING: directory '/oracle/app/oracle' is not owned by root
WARNING: directory '/oracle/app' is not owned by root
WARNING: directory '/oracle' is not owned by root
Checking to see if Oracle CRS stack is already configured
Checking to see if any 9i GSD is up

Setting the permissions on OCR backup directory
Setting up NS directories
Oracle Cluster Registry configuration upgraded successfully
WARNING: directory '/oracle/app/oracle/product' is not owned by root
WARNING: directory '/oracle/app/oracle' is not owned by root
WARNING: directory '/oracle/app' is not owned by root
WARNING: directory '/oracle' is not owned by root
Successfully accumulated necessary OCR keys.
Using ports: CSS=49895 CRS=49896 EVMC=49898 and EVMR=49897.
node <nodenumber>: <nodename> <private interconnect name> <hostname>
node 0: cwxndb01 cwxndb01-priv cwxndb01
Creating OCR keys for user 'root', privgrp 'sys'..
Operation successful.
Now formatting voting device: /dev/vx/rdsk/vgdata01/lv_vote_128m_01
Now formatting voting device: /dev/vx/rdsk/vgdata02/lv_vote_128m_02
Now formatting voting device: /dev/vx/rdsk/vgdata03/lv_vote_128m_03
Format of 3 voting devices complete.
Startup will be queued to init within 30 seconds.

然后就在显示完"Startup will be queued to init within 30 seconds"之后就一直挂起。在安装CRS运行root.sh时,一般失败的情形是报错然后退出,很少有一直挂起的。

为了解决一个未知的问题,我们通常分为3步,了解当前正在做什么,当前所做的为什么会引起问题,解决问题。那么现在,我们需要知道的是root.sh这个脚本正在做什么,执行到了哪里。

  • 使用"ps -ef | grep root.sh"命令,找到root.sh的pid,然后再寻找这个pid的子进程,一直定位到最末端,发现当前正在运行的命令是"sleep 60",而"sleep 60"的上级命令是"init.cssd startcheck CSS"。
  • root.sh的核心其实在于$ORA_CRS_HOME/install/rootconfig这个脚本,在这个文件中,我们根据上述线索,找到下面的代码:
    # Prepare to start the daemons.
    $ID/init.crs start
    
    # Check to see if they are going to start.
    $ID/init.cssd startcheck CSS
    if [ "$?" != "0" ]; then
      $ECHO CRS daemons not set to start. See $MSGFILE for details.
      exit 1
    fi
    

    实际上运行root.sh的输出中的"Startup will be queued to init within 30 seconds.",正是init.crs start的显示。从root.sh当前运行的子进程来看,目前正在运行"$ID/init.cssd startcheck CSS"这一命令。所以接下来我们需要知道在这一个命令里面会一直挂起。

在HP-UX操作系统中,init.cssd在/sbin/init.d目录下,我们可以发现init.cssd也是一个比较复杂的脚本。为了搞清楚一个shell脚本执行到了哪里出了问题,我们可以使用sh -x以调试方式运行shell脚本。(实际上在分析root.sh当前正在进行的操作时,我们同样可以用sh -x ./root.sh这样的命令来运行。)

我们手工执行如下的命令:

 
   cd /sbin/init.d
   sh -x init.cssd startcheck CSS

然后发现在下面的代码中32-37行代码处挂起:


# check on vendor clusterware and filesystem dependencies.
# Startcheck is supposed to block for a long time waiting on dependencies.
'startcheck') # Returns 0 if we should start
              # Returns 1 on a non-cluster boot
              # Returns 2 if disabled by admin
              # Returns 3 on error

    # Check our startup run status, initialized by the automatic startup
    # or manual startup routes.
    $ID/init.cssd runcheck
    STATUS=$?
    if [ "$STATUS" != "0" ]; then
      exit $STATUS;
    fi

    # If we have vendor clusterware, and CLINFO indicates a non-clustered boot
    # then prevent CRS startup.
    if [ $USING_VC -eq 1 ]
    then
      # Run CLINFO to get cluster status. It returns 0 if the machine is
      # booting in clustered mode.
      $EVAL $CLINFO
      if [ "$?" != "0" ]; then
        $LOGMSG "Oracle Cluster Ready Services disabled by non-clustered boot."
        $ID/init.cssd norun
        exit 1;
      fi

      # Wait for the Vendor Clusterware to start
      $EVAL $VC_UP
      RC=$?
      while [ $RC -ne 0 ]; do
        $LOGMSG "Oracle Cluster Ready Services waiting for $VC_NAME to start."
        $SLEEP $DEP_CHECK_WAIT
        $EVAL $VC_UP
        RC=$?
      done
    fi

32-37行代码是一个循环,从代码注释上看,是在等待Vendor Clusterware启动。如果一直没有启动,就一直循环,也就是一直挂起。代码”$EVAL $VC_UP“用于判断Vendon Clusterware是否已启动。这里$VC_UP是什么值呢,在文件中搜索代码,可以找到如下的代码:

HP-UX) MACH_HARDWARE=`/bin/uname -m`
       CLUSTERDIR=/opt/nmapi/nmapi2
       if [ "$MACH_HARDWARE" = "ia64" ]; then
          SO_EXT=so
          NMAPIDIR_64=$CLUSTERDIR/lib/hpux64
       else
          SO_EXT=sl
          NMAPIDIR_64=$CLUSTERDIR/lib/pa20_64
       fi
       LD_LIBRARY_PATH=$ORA_CRS_HOME/lib:$NMAPIDIR_64:/usr/lib:$LD_LIBRARY_PATH
       export LD_LIBRARY_PATH
       # Presence of this file indicates that vendor clusterware is installed
       SKGXNLIB=${NMAPIDIR_64}/libnmapi2.${SO_EXT}
       if [ -f $SKGXNLIB ]; then
         USING_VC=1
       fi

       VC_UP="$PSEF | $GREP '/usr/lbin/cm[g]msd' 1>$NULL 2>$NULL"
       # NOTE: Not supporting Hardware partitioning machines.
       REBOOT_TOC="/var/opt/oracle/bin/reboot_toc"
       if [ -x $REBOOT_TOC ]; then
       FAST_REBOOT="$REBOOT_TOC || /usr/sbin/reboot -r -n -q"
       SLOW_REBOOT="/bin/kill -HUP `$CAT /var/run/syslog.pid` ; /bin/sync & $SLEEP 2 ; $REBOOT_TOC || /usr/sbin/reboot -r -n -q"
       else
         FAST_REBOOT="/usr/sbin/reboot -r -n -q"
         SLOW_REBOOT="/bin/kill -HUP `$CAT /var/run/syslog.pid` ; /bin/sync & $SLEEP 2 ; /usr/sbin/reboot -r -n -q"
       fi
       DFL_CLSINFO=$CLUSTERDIR/bin/clsinfo
       if [ $USING_VC -eq 0 ]; then
          # We are not using vendor clusterware.
          VC_NAME=""
          VC_UP=/bin/true
          CLINFO=/bin/true
          # We do not have a slow reboot without vendor clusterware
          SLOW_REBOOT=$FAST_REBOOT
       elif [ -f $DFL_CLSINFO ]; then
          # We are using Generic HP Vendor Clusterware
          VC_NAME="Generic HP-UX Vendor Clusterware"
          VC_UP=/bin/true
          CLINFO=$DFL_CLSINFO
       else
          # We are using HPUX ServiceGuard Clusterware
          VC_NAME="HP-UX Service Guard"
          CLINFO=/bin/true
       fi

       ID=/sbin/init.d

以上一段代码的主要意思是:如果存在/opt/nmapi/nmapi2/libnmapi2.so文件,那么表示系统使用了Vendor Clusterware,也就是使用了第三方集群软件。如果存在/opt/nmapi/nmapi2/bin/clsinfo文件,则表示系统使用了其他的第三方集群软件,并且该文件可以执行用于判断是否已经启动Vendor Clusterware,即VC_UP=/opt/nmapi/nmapi2/bin/clsinfo。如果不存在该文件,表示系统使用的是HP ServiceGuard,那么VC_UP="$PSEF | $GREP '/usr/lbin/cm[g]msd' 1>$NULL 2>$NULL" ,即判断cm开头的一些进程是否在运行,用来判断Service Guard是否在运行。从sh -x执行结果来看,脚本认为系统用的是Service Guard,很显然,这个是错误的,一直在等待Service Guard启动,那这样很明显就会一直等待而挂起。

那么到目前为止,可以知道是由于错误地判断了Vendor Clusterware引起了问题,那么用于判断Vendor Clusterware的文件/opt/nmapi/nmapi2/bin/clsinfo应该不存在于系统中。/opt/nmapi/nmapi2/bin目录下的确没有clsinfo这个文件。而/opt/nmapi/nmapi2/lib目录下存在相应的库文件:

cwxndb01:[/opt/nmapi/nmapi2/lib/hpux64]#ll
total 0
lr-xr-xr-x   1 bin        bin             27 Jul 18 13:33 libnmapi2.so -> /usr/lib/hpux64/libvcsmm.so

可以看到库文件用的是symantec sfrac的库文件。
而bin目录下没有任意文件,看起来应该是symantec sfrac安装的BUG。

那怎么样来解决这个问题呢,知道了原因,解决就很简单了:

cwxndb01:[/opt/nmapi/nmapi2]#cd /opt/nmapi/nmapi2/bin
cwxndb01:[/opt/nmapi/nmapi2/bin]#ln -s /opt/VRTSvcs/ops/bin/clsinfo

只需要在这个目录下建一个链接即可。做完上述操作后,再清理掉环境,重新运行root.sh,一切顺利。

这个问题引起的根本原因,看起来是Symantec SFRAC安装的一个bug。不过总的来说,新版本的SFRAC环境下安装RAC数据库已经方便很多,跟普通方式安装CRS没有任何区别,而早期版本的SFRAC环境下安装RAC,需要使用SFRAC提供的专用界面进行安装。

--The End---

,

客户的一套重要生产系统,出现了性能问题。这个问题涉及的信息如下:

  • 数据库主机的CPU利用率长期在100%左右。
  • 应用系统在晚上进行调整后,开始出现了问题。
  • 数据库中出现大量的latch: shared pool、latch: library cache、cursor: pin S、latch: cache buffers chains和latch: cache buffers lru chain等各种等待。
  • 由于系统较大,整个系统实际上被分成了2个部分,每个部分服务不同的用户。二者的应用基本相同,分别对应2套数据库,二者也同样在出问题之前进行了调整,而数据库的主机配置及参数也是相同的,同时均为10.2.0.5双节点RAC数据库。但是只有其中1套库有性能问题。
  • 2套库的配置虽然没有差别,但是负载方式却有很大的区别,正常的那套库,2个节点的负载基本上是均衡的,而现在有性能问题的这套库,所有的负载基本上全部在第1个节点上(虽然已经多次要求开发商整改,不幸的是...这里不用说了)。

下面是AWR报告中的数据:

性能正常时间段的数据(采集时间2小时):

              Snap Id      Snap Time      Sessions Curs/Sess
            --------- ------------------- -------- ---------
Begin Snap:      3408 19-Sep-11 09:00:21     4,690      39.5
  End Snap:      3412 19-Sep-11 11:00:07     4,950      38.7
   Elapsed:              119.76 (mins)
   DB Time:            2,900.95 (mins)

Cache Sizes
~~~~~~~~~~~                       Begin        End
                             ---------- ----------
               Buffer Cache:    35,840M    35,840M  Std Block Size:         8K
           Shared Pool Size:     6,144M     6,144M      Log Buffer:    30,632K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:          1,968,314.84              6,360.04
              Logical reads:          1,038,182.89              3,354.59
              Block changes:            148,577.35                480.09
             Physical reads:              3,541.98                 11.44
            Physical writes:                546.31                  1.77
                 User calls:              7,811.87                 25.24
                     Parses:              3,187.30                 10.30
                Hard parses:                 10.07                  0.03
                      Sorts:              6,258.90                 20.22
                     Logons:                  9.65                  0.03
                   Executes:             24,100.52                 77.87
               Transactions:                309.48
Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   99.69    In-memory Sort %:  100.00
            Library Hit   %:   99.36        Soft Parse %:   99.68
         Execute to Parse %:   86.77         Latch Hit %:   99.64
Parse CPU to Parse Elapsd %:   45.61     % Non-Parse CPU:   99.46

 Shared Pool Statistics        Begin    End
                              ------  ------
             Memory Usage %:   95.44   94.46
    % SQL with executions>1:   89.10   77.18
  % Memory for SQL w/exec>1:   87.82   76.62

Top 5 Timed Events                                         Avg %Total
~~~~~~~~~~~~~~~~~~                                        wait   Call
Event                                 Waits    Time (s)   (ms)   Time Wait Class
------------------------------ ------------ ----------- ------ ------ ----------
CPU time                                        114,107          65.6
db file sequential read           8,232,067      40,956      5   23.5   User I/O
db file scattered read              929,509       2,392      3    1.4   User I/O
log file sync                     2,119,459       2,372      1    1.4     Commit
SQL*Net message from dblink       1,375,619       1,576      1    0.9    Network
          -------------------------------------------------------------
^LWait Events                         DB/Inst: CRM2DB/crm2db1  Snaps: 3408-3412
-> s  - second
-> cs - centisecond -     100th of a second
-> ms - millisecond -    1000th of a second
-> us - microsecond - 1000000th of a second
-> ordered by wait time desc, waits desc (idle events last)
-> %Timeouts: value of 0 indicates value was <  .5%.  Value of null is truly 0

                                                                  Avg
                                            %Time  Total Wait    wait     Waits
Event                                 Waits -outs    Time (s)    (ms)      /txn
---------------------------- -------------- ----- ----------- ------- ---------
db file sequential read           8,232,067   N/A      40,956       5       3.7
db file scattered read              929,509   N/A       2,392       3       0.4
log file sync                     2,119,459     0       2,372       1       1.0
SQL*Net message from dblink       1,375,619   N/A       1,576       1       0.6
SQL*Net more data from clien      1,871,442   N/A       1,428       1       0.8
control file sequential read      3,010,438   N/A       1,179       0       1.4
Backup: sbtbackup                        12   N/A       1,167   97243       0.0
gc cr grant 2-way                 3,688,572   N/A         993       0       1.7
log file parallel write           2,205,552   N/A         974       0       1.0
gc cr multi block request         2,378,748   N/A         721       0       1.1
gc current block 2-way            1,941,104   N/A         693       0       0.9
SQL*Net more data to client       5,142,664   N/A         498       0       2.3
gc buffer busy                      166,717     0         428       3       0.1
direct path read                    424,645   N/A         344       1       0.2
db file parallel read                61,816   N/A         298       5       0.0
enq: HW - contention                 17,487     0         216      12       0.0
db file parallel write              221,865   N/A         210       1       0.1
Backup: sbtwrite2                    36,271   N/A         145       4       0.0
gc current grant 2-way              445,456   N/A         115       0       0.2
enq: TM - contention                    255    74         102     400       0.0
                         

在应用调整第2天上午的awr数据(采集时长1小时):

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:          2,420,236.58              6,734.48
              Logical reads:          1,277,888.60              3,555.82
              Block changes:            164,638.57                458.12
             Physical reads:              1,208.59                  3.36
            Physical writes:                456.79                  1.27
                 User calls:              8,785.42                 24.45
                     Parses:              4,103.35                 11.42
                Hard parses:                 12.87                  0.04
                      Sorts:              8,052.30                 22.41
                     Logons:                  9.05                  0.03
                   Executes:             32,573.41                 90.64
               Transactions:                359.38

  % Blocks changed per Read:   12.88    Recursive Call %:    85.01
 Rollback per transaction %:    0.19       Rows per Sort:     8.74

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.97       Redo NoWait %:  100.00
            Buffer  Hit   %:   99.92    In-memory Sort %:  100.00
            Library Hit   %:   99.92        Soft Parse %:   99.69
         Execute to Parse %:   87.40         Latch Hit %:   99.33
Parse CPU to Parse Elapsd %:   29.94     % Non-Parse CPU:   99.17

 Shared Pool Statistics        Begin    End
                              ------  ------
             Memory Usage %:   95.42   95.02
    % SQL with executions>1:   61.41   72.38
  % Memory for SQL w/exec>1:   60.04   73.07

Top 5 Timed Events                                         Avg %Total
~~~~~~~~~~~~~~~~~~                                        wait   Call
Event                                 Waits    Time (s)   (ms)   Time Wait Class
------------------------------ ------------ ----------- ------ ------ ----------
CPU time                                         69,736          56.2
Backup: sbtwrite2                 2,319,231      22,510     10   18.1 Administra
db file sequential read           3,476,830      20,923      6   16.9   User I/O
latch: shared pool                  159,416       9,280     58    7.5 Concurrenc
Backup: sbtbackup                        28       2,746  98056    2.2 Administra
          -------------------------------------------------------------
^LWait Events                         DB/Inst: CRM2DB/crm2db1  Snaps: 3602-3604
-> s  - second
-> cs - centisecond -     100th of a second
-> ms - millisecond -    1000th of a second
-> us - microsecond - 1000000th of a second
-> ordered by wait time desc, waits desc (idle events last)
-> %Timeouts: value of 0 indicates value was <  .5%.  Value of null is truly 0

                                                                  Avg
                                            %Time  Total Wait    wait     Waits
Event                                 Waits -outs    Time (s)    (ms)      /txn
---------------------------- -------------- ----- ----------- ------- ---------
Backup: sbtwrite2                 2,319,231   N/A      22,510      10       1.8
db file sequential read           3,476,830   N/A      20,923       6       2.7
latch: shared pool                  159,416   N/A       9,280      58       0.1
Backup: sbtbackup                        28   N/A       2,746   98056       0.0
log file sync                     1,283,319     0       2,538       2       1.0
gc buffer busy                    1,088,432     0       2,324       2       0.8
latch: library cache                 31,278   N/A       1,328      42       0.0
control file sequential read      1,512,218   N/A       1,082       1       1.2
log file parallel write           1,157,491   N/A       1,045       1       0.9
SQL*Net message from dblink         952,956   N/A         987       1       0.7
SQL*Net more data from clien        962,454   N/A         837       1       0.7
kst: async disk IO                   17,574   N/A         483      27       0.0
gc cr grant 2-way                 1,539,289   N/A         439       0       1.2
gc current block 2-way              946,330   N/A         353       0       0.7
direct path read                    291,853   N/A         313       1       0.2
db file parallel write              261,735   N/A         298       1       0.2
read by other session                92,451     0         270       3       0.1
cursor: pin S                   304,676,088   N/A         233       0     235.1
db file scattered read               28,062   N/A         132       5       0.0
enq: TX - row lock contentio            466    52         123     263       0.0
direct path write                   227,989   N/A         113       0       0.2
          

从Load profile数据对比来看,应用调整后系统负载有一定的提高,同时每事务逻辑读也有一定的增加(不足10%)。这会是个问题吗?
从第2份数据来看,latch: shared pool 和 latch: library cache有明显的增加。

下面的是性能问题更加严重的时候的AWR数据(采集时长为2小时):

              Snap Id      Snap Time      Sessions Curs/Sess
            --------- ------------------- -------- ---------
Begin Snap:      3744 26-Sep-11 09:00:01     4,504      38.2
  End Snap:      3748 26-Sep-11 11:00:26     4,916      52.7
   Elapsed:              120.41 (mins)
   DB Time:           21,868.27 (mins)

Cache Sizes
~~~~~~~~~~~                       Begin        End
                             ---------- ----------
               Buffer Cache:    35,840M    35,840M  Std Block Size:         8K
           Shared Pool Size:     6,144M     6,144M      Log Buffer:    30,632K

Load Profile
~~~~~~~~~~~~                            Per Second       Per Transaction
                                   ---------------       ---------------
                  Redo size:          1,790,430.57              5,945.33
              Logical reads:          1,162,875.15              3,861.46
              Block changes:            154,064.15                511.59
             Physical reads:              3,007.80                  9.99
            Physical writes:                337.21                  1.12
                 User calls:              8,227.28                 27.32
                     Parses:              3,603.24                 11.96
                Hard parses:                 11.25                  0.04
                      Sorts:              7,304.40                 24.26
                     Logons:                 11.27                  0.04
                   Executes:             25,994.53                 86.32
               Transactions:                301.15

  % Blocks changed per Read:   13.25    Recursive Call %:    82.87
 Rollback per transaction %:    0.21       Rows per Sort:     8.14

Instance Efficiency Percentages (Target 100%)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            Buffer Nowait %:   99.99       Redo NoWait %:  100.00
            Buffer  Hit   %:   99.75    In-memory Sort %:  100.00
            Library Hit   %:   99.94        Soft Parse %:   99.69
         Execute to Parse %:   86.14         Latch Hit %:   98.90
Parse CPU to Parse Elapsd %:   14.69     % Non-Parse CPU:   84.51

 Shared Pool Statistics        Begin    End
                              ------  ------
             Memory Usage %:   95.48   94.33
    % SQL with executions>1:   87.63   74.92
  % Memory for SQL w/exec>1:   86.52   74.66

Top 5 Timed Events                                         Avg %Total
~~~~~~~~~~~~~~~~~~                                        wait   Call
Event                                 Waits    Time (s)   (ms)   Time Wait Class
------------------------------ ------------ ----------- ------ ------ ----------
latch: shared pool                  991,918     274,715    277   20.9 Concurrenc
latch: library cache                941,443     232,344    247   17.7 Concurrenc
CPU time                                        212,150          16.2
cursor: pin S                  ############     115,049      0    8.8      Other
latch: cache buffers lru chain    1,106,570      96,442     87    7.4      Other
          -------------------------------------------------------------
^LWait Events                         DB/Inst: CRM2DB/crm2db1  Snaps: 3744-3748
-> s  - second
-> cs - centisecond -     100th of a second
-> ms - millisecond -    1000th of a second
-> us - microsecond - 1000000th of a second
-> ordered by wait time desc, waits desc (idle events last)
-> %Timeouts: value of 0 indicates value was < .5%.  Value of null is truly 0

                                                                  Avg
                                            %Time  Total Wait    wait     Waits
Event                                 Waits -outs    Time (s)    (ms)      /txn
---------------------------- -------------- ----- ----------- ------- ---------
latch: shared pool                  991,918   N/A     274,715     277       0.5
latch: library cache                941,443   N/A     232,344     247       0.4
cursor: pin S                 9,095,470,035   N/A     115,049       0   4,180.5
latch: cache buffers lru cha      1,106,570   N/A      96,442      87       0.5
db file sequential read           7,819,285   N/A      49,564       6       3.6
latch: cache buffers chains         511,936   N/A      35,753      70       0.2
latch free                           95,257     0      14,609     153       0.0
latch: object queue header o        221,907   N/A      14,378      65       0.1
log file sync                     2,156,343     1      11,496       5       1.0
cursor: pin S wait on X             422,320   100       4,603      11       0.2
gc cr grant 2-way                 3,526,856     0       3,430       1       1.6
db file scattered read              624,480   N/A       3,083       5       0.3
enq: TX - row lock contentio          5,946    88       2,600     437       0.0
latch: row cache objects             16,983   N/A       2,281     134       0.0
SQL*Net message from dblink       1,284,487   N/A       2,179       2       0.6
gc buffer busy                      353,228     0       2,054       6       0.2
gc cr multi block request         2,499,709     0       1,974       1       1.1
gc current block 2-way            1,969,516     0       1,920       1       0.9
SQL*Net more data from clien      1,667,513   N/A       1,914       1       0.8
control file sequential read      2,974,349   N/A       1,735       1       1.4
db file parallel read               230,795   N/A       1,645       7       0.1
log file parallel write           1,581,432   N/A       1,303       1       0.7
Backup: sbtbackup                        14   N/A         730   52120       0.0
buffer busy waits                   143,869     0         488       3       0.1
latch: library cache lock             5,058   N/A         378      75       0.0
enq: HW - contention                 22,039     0         370      17       0.0
gc current grant 2-way              424,243   N/A         357       1       0.2
enq: SQ - contention                  5,010     1         354      71       0.0
read by other session                50,153     0         339       7       0.0
direct path read                    324,226   N/A         305       1       0.1
db file parallel write              207,793   N/A         274       1       0.1
row cache lock                      175,301     0         263       1       0.1
          

从第3份数据来看,性能更为恶化,情形更为复杂。

那么针对这个性能现象,可以提出如下的问题:

  • 最直观的,最容易想到的就是,性能问题的出现是否与应用调整有关,如果是,为什么另一套库没有出现问题?会不会是另一套库的负载在2个节点都有相对均衡的负担,而出问题的库,负载全部集中在1个节点上引起?
  • 客户是在应用调整几天后才报告性能问题,这个问题会不会是一个逐渐出现的问题?如果一开始就有严重的性能问题,那么应该会很快报告。不过中间跨了一个周末,所以对于”逐渐出现问题“的判断又加了一些难度。
  • 这么多的性能问题相关的现象中,哪个更贴近问题的原因?实际上在主机的CPU长期保持在100%左右时,会放大平时的一些轻微的问题,或者引起另一些问题。从等待来看,latch: cache buffers chains和cursor: pin S都会引起CPU的急剧增加,而其他的latch竞争同样也会引起CPU的上升,虽然上升没有前者大。到底是SQL执行效率不高引起CPU急剧增加然后引起了shared pool latch等与解析相关的资源争用还是因为解析相关的问题导致CPU急剧增加引起了cache buffers chains等与SQL执行相关的latch争用?或者是2者的共同作用?

下面首先来分析,会不会是应用调整引起的问题,也就是说,是不是由SQL引起的问题。如果是SQL引起的问题,会有几种可能,1是部分频繁执行的SQL执行效率变差;2是增加了很多的硬解析;3是并发增加或者说是SQL的执行次数有上升。仔细检查对比现在和以前的AWR数据,可以排除第1和2这种可能。至于第3种,可能略有增加,但是不一定跟应用调整有关,毕竟正常情况下业务量的变化也是有可能的。所以应用调整引起问题的可能性较小。

会不会是因为负载过大引起的问题,这个很容易验证。取业务低谷期,比如下班时间的数据,进行分析可以发现,既然在CPU在稍低(75%以下)的时候,仍然有大量的library cache latch的争用。下面是取自周日19:00-20:00的AWR数据(注:本文涉及故障的分析时间是在星期一):

Top 5 Timed Events                                         Avg %Total
~~~~~~~~~~~~~~~~~~                                        wait   Call
Event                                 Waits    Time (s)   (ms)   Time Wait Class
------------------------------ ------------ ----------- ------ ------ ----------
CPU time                                         49,224          48.3
latch: shared pool                  185,951      41,178    221   40.4 Concurrenc
latch: library cache                 45,636       8,861    194    8.7 Concurrenc
db file sequential read             815,066       4,763      6    4.7   User I/O
cursor: pin S                  ############       1,732      0    1.7      Other
          -------------------------------------------------------------

                                                                  Avg
                                            %Time  Total Wait    wait     Waits
Event                                 Waits -outs    Time (s)    (ms)      /txn
---------------------------- -------------- ----- ----------- ------- ---------
latch: shared pool                  185,951   N/A      41,178     221       0.6
latch: library cache                 45,636   N/A       8,861     194       0.2
db file sequential read             815,066   N/A       4,763       6       2.8
cursor: pin S                 1,890,998,700   N/A       1,732       0   6,459.2
latch free                            8,549     0       1,278     150       0.0
control file sequential read      1,507,789   N/A         466       0       5.2
Backup: sbtbackup                         7   N/A         428   61096       0.0
log file sync                       285,442     0         309       1       1.0
SQL*Net more data from clien        145,137   N/A         221       2       0.5
db file scattered read               67,716   N/A         173       3       0.2
gc buffer busy                       56,842     0         135       2       0.2
gc cr grant 2-way                   359,146   N/A          99       0       1.2
SQL*Net message from dblink         123,206   N/A          98       1       0.4
log file parallel write             289,048   N/A          91       0       1.0
SQL*Net more data to client       1,242,471   N/A          79       0       4.2
gc current block 2-way              179,654   N/A          66       0       0.6
direct path read                     62,336   N/A          65       1       0.2
gc cr multi block request           228,693   N/A          52       0       0.8
          

至于性能问题是不是逐渐引起的,这个问题目前并不关键,暂时不考虑。只是做为后面分析问题的一个参考。

Read the rest of this entry

,

客户一套运行在Oracle 10.2.0.5 RAC上的系统,间歇性地出现性能问题。其性能现象为前台反映性能缓慢,从系统上看CPU利用率大幅增加,load增加。这种性能问题通常在出现几分钟后自动恢复正常。

从AWR中的TOP 5等待来看:

Top 5 Timed Events                                         Avg %Total
~~~~~~~~~~~~~~~~~~                                        wait   Call
Event                                 Waits    Time (s)   (ms)   Time Wait Class
------------------------------ ------------ ----------- ------ ------ ----------
latch: cache buffers lru chain      774,812     140,185    181   29.7      Other
gc buffer busy                    1,356,786      61,708     45   13.1    Cluster
latch: object queue header ope      903,456      55,089     61   11.7      Other
latch: cache buffers chains         360,522      49,016    136   10.4 Concurrenc
gc current grant busy               112,970      19,893    176    4.2    Cluster
          -------------------------------------------------------------

可以看到,TOP 5中,有3个是latch相关的等待,而另外2个则是跟RAC相关的等待。
如果再查看更细的等待数据,可以发现其他问题:


                                                                  Avg
                                            %Time  Total Wait    wait     Waits
Event                                 Waits -outs    Time (s)    (ms)      /txn
---------------------------- -------------- ----- ----------- ------- ---------
latch: cache buffers lru cha        774,812   N/A     140,185     181       1.9
gc buffer busy                    1,356,786     6      61,708      45       3.3
latch: object queue header o        903,456   N/A      55,089      61       2.2
latch: cache buffers chains         360,522   N/A      49,016     136       0.9
gc current grant busy               112,970    25      19,893     176       0.3
gcs drm freeze in enter serv         38,442    97      18,537     482       0.1
gc cr block 2-way                 1,626,280     0      15,742      10       3.9
gc remaster                           6,741    89      12,397    1839       0.0
row cache lock                       52,143     6       9,834     189       0.1

从上面的数据还可以看到,除了TOP 5等待,还有"gcs drm freeze in enter server mode“以及"gc remaster"这2种比较少见的等待事件,从其名称来看,明显与DRM有关。那么这2种等待事件与TOP 5的事件有没有什么关联?。MOS文档"Bug 6960699 - "latch: cache buffers chains" contention/ORA-481/kjfcdrmrfg: SYNC TIMEOUT/ OERI[kjbldrmrpst:!master] [ID 6960699.8]”提及,DRM的确可能会引起大量的"latch: cache buffers chains"、"latch: object queue header operation"等待,虽然文档没有提及,但不排除会引起”latch: cache buffers lru chain“这样的等待。

为了进一步证实性能问题与DRM相关,使用tail -f命令监控LMD后台进程的trace文件。在trace文件中显示开始进行DRM时,查询v$session视图,发现大量的 "latch: cache buffers chains" 、"latch: object queue header operation"等待事件,同时有"gcs drm freeze in enter server mode“和"gc remaster"等待事件,同时系统负载升高,前台反映性能下降。而在DRM完成之后,这些等待消失,系统性能恢复到正常。

看起来,只需要关闭DRM就能避免这个问题。怎么样来关闭/禁止DRM呢?很多MOS文档提到的方法是设置2个隐含参数:

_gc_affinity_time=0
_gc_undo_affinity=FALSE

不幸的是,这2个参数是静态参数,也就是说必须要重启实例才能生效。
实际上可以设置另外2个动态的隐含参数,来达到这个目的。按下面的值设置这2个参数之后,不能完全算是禁止/关闭了DRM,而是从”事实上“关闭了DRM。

_gc_affinity_limit=250
_gc_affinity_minimum=10485760

甚至可以将以上2个参数值设置得更大。这2个参数是立即生效的,在所有的节点上设置这2个参数之后,系统不再进行DRM,经常一段时间的观察,本文描述的性能问题也不再出现。

下面是关闭DRM之后的等待事件数据:

Top 5 Timed Events                                         Avg %Total
~~~~~~~~~~~~~~~~~~                                        wait   Call
Event                                 Waits    Time (s)   (ms)   Time Wait Class
------------------------------ ------------ ----------- ------ ------ ----------
CPU time                                         15,684          67.5
db file sequential read           1,138,905       5,212      5   22.4   User I/O
gc cr block 2-way                   780,224         285      0    1.2    Cluster
log file sync                       246,580         246      1    1.1     Commit
SQL*Net more data from client       296,657         236      1    1.0    Network
          -------------------------------------------------------------
          
                                                                  Avg
                                            %Time  Total Wait    wait     Waits
Event                                 Waits -outs    Time (s)    (ms)      /txn
---------------------------- -------------- ----- ----------- ------- ---------
db file sequential read           1,138,905   N/A       5,212       5       3.8
gc cr block 2-way                   780,224   N/A         285       0       2.6
log file sync                       246,580     0         246       1       0.8
SQL*Net more data from clien        296,657   N/A         236       1       1.0
SQL*Net message from dblink          98,833   N/A         218       2       0.3
gc current block 2-way              593,133   N/A         218       0       2.0
gc cr grant 2-way                   530,507   N/A         154       0       1.8
db file scattered read               54,446   N/A         151       3       0.2
kst: async disk IO                    6,502   N/A         107      16       0.0
gc cr multi block request           601,927   N/A         105       0       2.0
SQL*Net more data to client       1,336,225   N/A          91       0       4.5
log file parallel write             306,331   N/A          83       0       1.0
gc current block busy                 6,298   N/A          72      11       0.0
Backup: sbtwrite2                     4,076   N/A          63      16       0.0
gc buffer busy                       17,677     1          54       3       0.1
gc current grant busy                75,075   N/A          54       1       0.3
direct path read                     49,246   N/A          38       1       0.2

那么,这里不得不提的是,什么是DRM?DRM对系统来说有什么好处?本文不再详述,因为下面的2篇文档已经描述得比较清楚,有兴趣的朋友可以参考:

关于本文所使用的2个隐含参数,在上述第二篇文档中也有详细描述。
--END---

当一个存储过程所依赖(引用的)对象发生某些更改时,会使得存储过程失效(invalidated),比如存储过程依赖(引用)的表增加减少了列、存储过程依赖(引用)的存储过程被重新编译。本文将介绍一种特殊的会引起存储过程失效的情况。

下面通过测试来演示与DB LINK相关的存储过程失效的情况:

1. 在TEST用户下创建到db1的DB LINK:

SQL> create database link to_db connect to perfstat identified by xxx using 'db1';

数据库链接已创建。

2.在TEST2用户下创建到db2的DB LINK,DB LINK的名称仍然为to_db,但实际上连接的数据库与TEST用户下to_db这个DB LINK连接的数据库并不是同一个数据库:

SQL> create database link to_db connect to perfstat identified by xxx using 'db2';

数据库链接已创建。

3. 在TEST用户下创建存储过程TEST_P1:

SQL> create or replace procedure test_p1
  2  is
  3    v_dbid number;
  4  begin
  5    select dbid into v_dbid from stats$snapshot@to_db where rownum<2;
  6  end;
  7  /

过程已创建。

SQL> select object_id,status from user_objects where object_name='TEST_P1';

 OBJECT_ID STATUS
---------- ----------
     18443 VALID

4. 在TEST2用户下创建存储过程TEST2_P1,这里存储过程TEST2_P1的代码与TEST用户下存储过程TEST_P1的代码明显不同。这两个存储过程的共同点是都引用了STATS$SNAPSHOT@TO_DB这个远程表。由于TEST和TEST2用户下TO_DB连接的是不同的数据库,因此这2个存储过程引用的STATS$SNAPSHOT@TO_DB位于不同的数据库上,也就是说是不同的表

SQL> create or replace procedure test2_p1
  2  is
  3    v_snap_time date;
  4  begin
  5    select snap_time into v_snap_time from stats$snapshot@to_db where rownum<2;
  6  end;
  7  /

过程已创建。

SQL> select object_id,status from user_objects where object_name='TEST2_P1';

 OBJECT_ID STATUS
---------- ----------
     18445 VALID

5. 在TEST用户下查看存储过程TEST_P1的状态,发现其状态为INVALID,而实际上这个时候这个存储过程以及其引用的对象没有任何变更:

SQL> select object_id,status from user_objects where object_name='TEST_P1';

 OBJECT_ID STATUS
---------- ----------
     18443 INVALID

6. 如果重新编译TEST.TEST_P1,那么其状态会成为VALID,但是这个时候TEST2.TEST2_P1则又莫名其妙地变成为INVALID:

SQL> alter procedure test.test_p1 compile;

过程已更改。

SQL> select object_id,owner,object_name,status from dba_objects 
  where object_name in ('TEST_P1','TEST2_P1');

 OBJECT_ID OWNER           OBJECT_NAME                    STATUS
---------- --------------- ------------------------------ ----------
     18443 TEST            TEST_P1                        VALID
     18445 TEST2           TEST2_P1                       INVALID

7. 在TEST2用户下执行存储过程TEST2_P1,然后再次检查存储过程状态:

SQL> exec test2_p1

PL/SQL 过程已成功完成。

SQL> select object_id,owner,object_name,status from dba_objects
  2  where object_name in ('TEST_P1','TEST2_P1');

 OBJECT_ID OWNER           OBJECT_NAME                    STATUS
---------- --------------- ------------------------------ ----------
     18443 TEST            TEST_P1                        INVALID
     18445 TEST2           TEST2_P1                       VALID

可以看到,TEST.TEST_P1失效了,而TEST2.TEST2_P1又正常了。

在这里就可以提出问题了:为什么创建了TEST2.TEST2_P1这个存储过程之后,TEST.TEST_P1就失效了?为什么TEST.TEST_P1重新编译之后TEST2.TEST2_P1又失效了?为什么TEST2.TEST2_P1重新执行(有一个隐式的编译过程)后,TEST.TEST_P1又失效了?

其实上以上三个问题可以归纳为一个问题:为什么TEST.TEST_P1和TEST2.TEST2_P1这2个存储过程,在其中一个状态正常的情况下,另一个为失效状态?

Read the rest of this entry

ORA-04031这个错误,几乎每一个专业的DBA都遇到过。这是一个相当严重的错误,Oracle进程在向SGA申请内存时,如果申请失败,则会报这个错误。大部分情况下是在向SGA中的shared pool申请内存时失败,而少有向large pool等池中申请内存失败。比如下面的报错:

Wed Apr 27 16:00:25 2011
Errors in file /oracle/app/oracle/admin/zxin/bdump/zxin1_ora_2052294.trc:
ORA-04031: unable to allocate 4128 bytes of shared memory 
("shared pool","unknown object","sga heap(3,0)","kgllk hash table")

这里很清楚地表示出来,是在向shared pool申请内存时失败。

shared pool内存申请(分配)失败,通常有如下的几种可能:

  • shared pool过小,比如在SGA Manual Management方式下,shared pool设置过小。比如一套数千连接的大系统,shared pool只设置了几百M。这种情况下,要解决问题很解单,增加shared pool的大小即可。
  • 应用没有使用绑定变量,硬解析非常多,导致shared pool内存碎片严重,分配大块内存时不能获得连续的内存空间。硬解析多的一个变种是虽然使用了绑定变量,但是由于某种原因,Cursor不能共享,导致Child Cursor非常多。实际上,如果shared pool较大(比如数GB大小),这种问题还是很少出现的,并且出现也通常出现在申请大块内存时。这种情况如果使用alter system flush shared_pool可以暂时缓解问题。但是这条命令又通常不适用于shared pool较大而且比较繁忙的系统。使用绑定变量
  • Cache的cursor很多,同时cursor_space_for_time这一参数设置为TRUE,可能会使shared pool碎片化严重,导致不能分配到大块的连续内存。
  • Oracle的BUG导致内存泄露,比如在一些版本中查询v$segment_statistics这样的视图导致内存泄露,使shared pool内存耗光。同样的情形还有类似于“obj stat memory”,"gcs resources","ges resources"等。通常这类内存为perm类型(permanet),这类内存通常是在分配时就确定了固定的用途,不能用于其他用途,因此极容易产生碎片。
  • Oracle从9i开始,根据shared pool的大小将shared pool分为多个子池(subpool),每个子池有独立的free list,同时在分配时单独管理(有其独立 的shared pool latch)。Oracle的BUG或者说是内存分配策略缺陷导致某一类shared pool的内存分配只在一个子池(subpool)中,即多个子池的使用极不均衡,导致向那个使用得最多的子池申请内存时失败。报错信息中的"sga heap(3,0)"即指明是在第3个子池申请内存时失败。本文案例中的ORA-04031错误其产生的原因可以归结为Oracle对shared pool的分配/使用策略问题。
  • 操作系统内存不足,这只会出现在shared pool的使用还没有达到最大值时才会出现,并且在操作系统都有swap的情况下,只有部分操作系统才可能有这种情况,比如在HP-UX下,reserved 内存过多导致swap满。
  • 其他原因,多数是因为BUG。请参考下面提及的MOS参考文档。

本文中的案例,其数据库是运行在AIX 5.3系统中的10.2.0.4 RAC,RAC节点数为2。数据库是从9i升级到10g,而目前处于正式升级前的测试阶段。数据库报的ORA-04031错误信息如本文前面所示(其中的数据库名称已经做了处理)。

在继续讲解案例之前,不得不提到MOS上的几篇关于ORA-04031错误的文档:

  • Master Note for Diagnosing ORA-4031 [ID 1088239.1]
  • Diagnosing and Resolving Error ORA-04031 on the Shared Pool or Other Memory Pools [Video] [ID 146599.1]
  • Interpreting the automatically generated ORA-4031 diagnostic trace. [ID 809560.1]
  • Troubleshooting and Diagnosing ORA-4031 Error [Video] [ID 396940.1]
  • ORA-4031 Common Analysis/Diagnostic Scripts [Video] [ID 430473.1]

其实分析ORA-04031错误,通常有以下几个要点:

  • 判断错误发生所有的内存区域,是shared pool,large pool还是streams pool等。这个很容易从错误信息中判断出来,本文主要描述shared pool的ORA-04031错误,这也是最常见的。
  • 检查Shared Pool的总大小以及free memory的大小。如果free memory看上去挺多,以subpool为单位检查是否存在是由于碎片导致没有足够的连续内存以供分配,特别是关注报错信息中提及的子池。
  • 如果Shared Pool相较于系统规模来说足够大(通常数GB都已经是很大的了),检查Shared Pool中有没有占用非常多的内存类型或内存组件,如果有,是什么样的类型的内存,在各个子池之间是否分布均匀。如果有异常占用较多的内存类型,根据此类型在MOS上搜寻是否是会有相应的BUG引起,或者分析这种类型的内存消耗较多的原因。比如如果是sql area很大,检查是不是硬解析特别多,或者是不是child cursor特别多引起。
  • 基于以上分析的数据,来判断shared pool内存分配失败的原因。

上面的步骤写得比较粗略,关于分析和解决ORA-04031问题,这里也有一篇不错的文章:Simplified Approach to Resolve ORA-4031

这里关键的是分析Shared Pool的内存数据。ORA-04031错误发生后如果有条件可以马上连接到数据库中查询相应的x$表和v$视图得到相应的数据,否则只能通过ORA-4031错误发生时产生的trace文件。_4031_dump_bitvec这个隐含参数用于控制发生ORA-04031错误时对SGA的dump行为,而trace文件的分析就不像使用SQL那样简单了。

下面再来详细地分析案例:
从错误信息来看,很显然,是向shared pool的第3个subpool申请内存时出错。
以下的数据是shared pool的数据:

Read the rest of this entry

ORA-01555错误是一种在Oracle数据库中很常见的错误。尤其在Oracle 8i及之前的版本最多。从9i开始的undo自动管理,至现在的10g、11g中的undo auto tuning,使得ORA-01555的错误越来越少。但是这个错误,仍然不可避免。而出现ORA-01555错误,通常有2种情况:

  • SQL语句执行时间太长,或者UNDO表空间过小,或者事务量过大,或者过于频繁的提交,导致执行SQL过程中进行一致性读时,SQL执行后修改的前镜像(即UNDO数据)在UNDO表空间中已经被覆盖,不能构造一致性读块。
  • SQL语句执行过程中,访问到的块,在进行延迟块清除时,不能确定该块的事务提交时间与SQL执行开始时间的先后次序。

第1种情况,是最常见的。解决的办法无非就是增加UNDO表空间大小,优化出错的SQL,或者避免频繁地提交。而第2种情况则是比第1种情况少很多。下面简单描述一下第2种情况发生的情景:

  • 有事务大量修改了A表的数据,或者A表的数据虽然被事务少量修改,但是一部分修改过的块已经刷出内存并写到了磁盘上。随即事务提交,提交时刻为SCN1。而提交时有数据块上的事务没有被清除。
  • 在SCN2时刻,开始执行SELECT查询A表,对A表进行全表扫描,而且A表很大。也可能是其他情况,比如是小表,但是是一个游标方式的处理过程,而处理过程中又非常耗时。注意,这里SCN2与SCN1之间可能相隔了很远,从时间上来说,甚至可能有数十天。不管怎么样,这在SCN1至SCN2时间之间,系统中存在大量的事务,使得UNDO表空间的块以及UNDO段头的事务表全部被重用过。
  • SELECT语句在读A表的一个块时,发现表上有活动事务,这是由于之前的事务没有清除所致。ORACLE根据数据块中ITL的XID检查事务表,这时会有2种情况:
    • XID对应的事务表中的记录仍然存在并发现事务已经提交,可以得到事务准确的提交SCN(commit scn),称为SCN3,等于SCN1。很显然,由于查询的时刻SCN2晚于事务提交的时刻SCN1,那么不需要构造一致性读块。
    • XID对应的事务表中的记录已经被重用,这个时候仍然表明表明事务已经被提交。那么这个时候,Oracle没办法准确地知道事务的提交时间,只能记录为这样一个事实,事务提交的SCN小于其UNDO段的事务表中最近一次重用的事务记录的SCN(即这个事务表最老的事务SCN)。这里称这个SCN为SCN4。
  • SCN4可能远小于SCN2,那是因为事务很早之前就已经提交。也可能SCN4大于SCN2,这是因为SELECT语句执行时间很长,同时又有大量的事务已经将事务表重用。对于后者,很显然,Oracle会认为该事务的提交时间可能在SELECT开始执行之后。这里为什么说可能,是因为ORACLE只能判断出事务是在SCN4之前提交的,并不是就刚好在SCN4提交。而此时,利用UNDO BLOCK进行一致性读数据的构造也很可能失败,因为UNDO BLOCK很可能已经被覆盖,特别是SCN1远小于SCN2的情况下。在这种情况下,ORA-01555错误就会出现。

对于上面最后一段,在SCN4大于SCN2的情况下,之后的描述,我提到了几个“可能”,是因为我对此也不能完全确定,Oracle是否还会有其他的方法来判断 事务的提交时间早于SCN2。而我自己的模拟测试始终没有模拟出ORA-01555。我的测试过程是这样子的:

  • 修改表T1,注意T1表已经足够大,比如几十万行数据以上。
  • flush buffer_cache,使未提交的事务修改的块全部刷出内存。
  • 提交事务。
  • 使用大量的事务(注意这些事务不含表T1),将UNDO表空间填满并确保所有事务表已经被全部重用过。
  • 写一段代码,以游标方式打开表T1,在游标的循环中使用dbms_lock.sleep故意增加时间。
  • 同时多个JOB会话产生大量与表T1无关的事务,将UNDO表空间填满并确保所有事务表已经被全部重用过。

在我的期望中,上面的测试,对于游标处理部分,应该会报ORA-01555错误。但实际测试并没有出现,对于这类情形,看起来Oracle还有其他的机制来发现块上的事务提交时间早于查询开始时间。

虽然测试没有达到预期的结果,但是对于事务提交后块没有清除引起的ORA-01555错误,需要几个充分的条件:表足够大,表上的事务提交后有没有事务清除的块,对大表进行长时间的查询比如全表扫描,查询开始后有大量的事务填充和UNDO表空间和重用了事务表。

而下面则是延迟块清除时引起的ORA-01555错误的一则案例。

首先进行简单的环境介绍,运行在HP-UX环境下的Oracle 10.2.0.3,主机只有4颗比较老的PA-RISC CPU。这个系统的特点是大数据量的批量处理,基本上都是大数据量的插入。每个月的数据有单独的表,表一般都是在几十G以上,大的表超过100G。CPU利用率长期保持在100%。

由于空间限制,需要定期将一些N个月之前的表导出备份到磁带上,然后将表删除。这些表,在导出时是不可能会有DML操作的。由于性能原因,导出时间过长(几个小时以上),在导出时经常会遇到ORA-01555错误而失败。这里不讨论怎么样提高性能使导出时间更短,这里只提出一种方法来解决ORA-01555错误。

从之前对ORA-01555错误的成因分析可以知道,这个ORA-01555错误,正是由于表上存在未清除的事务,同时导出时间过长,UNDO段头的事务表被全部重用,ORACLE在查询到有未清除事务的块时不能确定事务提交时间是否早于导出(查询)开始时间,这时候就报ORA-01555错误。

要解决这个错误,除了提高性能,那么从另一个角度来思考这个问题,可以想办法先清除掉表上的事务(即延迟块清除)。那么我们可以通过一个简单的SELECT语句来解决:

SELECT /*+ FULL(A) */ COUNT(*) FROM BIG_TABLE A;

SELECT COUNT(*),速度显然大大高于SELECT *,所需的时间也更短,出现ORA-01555错误的可能性就非常低了。

注意这里需要加上FULL HINT,以避免查询进行索引快速全扫描,而不是对表进行全表扫描。另外,需要注意的是,这里不能为了提高性能而使用PARALLEL(并行),测试表明,在表上进行并行查询,以DIRECT READ方式读取表并不会清除掉表上的事务。

如果表过大,SELECT COUNT(*)的时间过长,那么我们可以用下面的代码将表分成多个段,进行分段查询。


  select dbms_rowid.rowid_create(1, oid1, fid1, bid1, 0) rowid1,
         dbms_rowid.rowid_create(1, oid2, fid2, bid2, 9999) rowid2
    from (select a.*, rownum rn
            from (select chunk_no,
                         min(oid1) oid1,
                         max(oid2) oid2,
                         min(fid1) fid1,
                         max(fid2) fid2,
                         min(bid1) bid1,
                         max(bid2) bid2
                    from (select chunk_no,
                                 FIRST_VALUE(data_object_id) OVER(PARTITION BY chunk_no ORDER BY data_object_id, relative_fno, block_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) oid1,
                                 LAST_VALUE(data_object_id) OVER(PARTITION BY chunk_no ORDER BY data_object_id, relative_fno, block_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) oid2,
                                 FIRST_VALUE(relative_fno) OVER(PARTITION BY chunk_no ORDER BY data_object_id, relative_fno, block_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) fid1,
                                 LAST_VALUE(relative_fno) OVER(PARTITION BY chunk_no ORDER BY data_object_id, relative_fno, block_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) fid2,
                                 FIRST_VALUE(block_id) OVER(PARTITION BY chunk_no ORDER BY data_object_id, relative_fno, block_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) bid1,
                                 LAST_VALUE(block_id + blocks - 1) OVER(PARTITION BY chunk_no ORDER BY data_object_id, relative_fno, block_id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) bid2
                            from (select data_object_id,
                                         relative_fno,
                                         block_id,
                                         blocks,
                                         ceil(sum2 / chunk_size) chunk_no
                                    from (select /*+ rule */ b.data_object_id,
                                                 a.relative_fno,
                                                 a.block_id,
                                                 a.blocks,
                                                 sum(a.blocks) over(order by b.data_object_id, a.relative_fno, a.block_id) sum2,
                                                 ceil(sum(a.blocks)
                                                      over() / &trunks) chunk_size
                                            from dba_extents a, dba_objects b
                                           where a.owner = b.owner
                                             and a.segment_name = b.object_name
                                             and nvl(a.partition_name, '-1') =
                                                 nvl(b.subobject_name, '-1')
                                             and b.data_object_id is not null
                                             and a.owner = upper('&owner')
                                             and a.segment_name = upper('&table_name'))))
                   group by chunk_no
                   order by chunk_no) a);

在上面的代码中trunks变量表示表分为的段数。
代入trunks,owner,table_name三条SQL,执行上面的代码,出来的结果类似如下:

ROWID1             ROWID2
------------------ ------------------
AAAER9AAIAAABGJAAA AAAER9AAIAAABIICcP
AAAER9AAIAAABIJAAA AAAER9AAIAAABMICcP
AAAER9AAIAAABMJAAA AAAER9AAIAAABQICcP
AAAER9AAIAAABQJAAA AAAER9AAIAAABWICcP
AAAER9AAIAAABWJAAA AAAER9AAIAAABaICcP
AAAER9AAIAAABaJAAA AAAER9AAIAAABcICcP
AAAER9AAIAAABcJAAA AAAER9AAIAAABgICcP
AAAER9AAIAAABgJAAA AAAER9AAIAAABkICcP
AAAER9AAIAAABkJAAA AAAER9AAIAAABoICcP
AAAER9AAIAAABoJAAA AAAER9AAIAAABsICcP

然后对每一个ROWID段执行类似下面的SQL:

SELECT /*+ NO_INDEX(A) */ COUNT(*) FROM BIG_TABLE A WHERE ROWID>='AAAER9AAIAAABGJAAA' AND ROWID< ='AAAER9AAIAAABIICcP';

对表进行分段处理,除了此处的用法,完全可以用于手工多进程处理大批量数据。更完整的功能已经在11g中实现,此处不做过多介绍。

对于本文提到的导出数据遇到ORA-01555错误的表,按上述方法处理后,问题得到解决,表顺利导出。

Mutex是Oracle从10g开始使用的一种数据结构,与latch极其类似,也是用于SGA内存结构的并发访问控制。在10g中,Mutex主要用于保护Cursor相关的内存结构,Parent Cursor, Child Cursor , Cursor Stat,同时兼有Pin Cursor的并发访问控制作用。

而从11g开始,Mutex又代替了library cache latch等多个与library cache相关的latch。

Mutex与latch相比,有哪些优点?我个人认为最重要的有2点:

  • Mutex通常在被保护对象内部,这样mutex的数量就多了很多,争用就更少。比如11g中,每一个Latch Latch Hash Bucket由一个mutex保护,而bucket数量高达131072,相比library cache latch最多只有67个child latch,数量上多了很多。很显然,分布在131072个Mutex上的竞争比67个Library cache latch上的竞争少得多。
  • Mutex从结构上来讲比latch更多简单,整个Mutex只有十几个字节,而Oracle在对Latch的结构优化和精简的情况下最少也有100多个字节。这样更小的结构意味着在处理Mutex时也只需要更少的指令,而更快。
  • Mutex代替Cursor上的library cache pin,比library cache pin更有效率。

本文主要描述如何通过system state dump来分析mutex的等待,因此对Mutex的工作机制以及相应的等待事件详解不做过多介绍。有机会我会在另外的文章中专门来讲这两部分。

我们来分析的system state dump来源于一个朋友,他的系统里面出现大量的'cursor: pin S wait on X'和'cursor: Mutex X'等待事件。他将产生的system state dump发给我,以分析出产生问题的root cause。由于system state dump比较在在,同时里面涉及的一些内容不适合放在网上,因此本文只给出分析过程,没有上传trace文件。数据库的版本是11.1.0.7。

首先试图找到'cursor: pin S wait on X'等待的阻塞进程,随便找到一个等待cursor:pin S wait on X的会话,在dump trace 文件中查找waiting for 'cursor: pin S wait on X':

    (session) sid: 1471 ser: 57513 trans: (nil), creator: 0x450d504f0
              flags: (0x45) USR/- flags_idl: (0x1) BSY/-/-/-/-/-
              flags2: (0x40008) -/-
              DID: , short-term DID: 
              txn branch: (nil)
              oct: 3, prv: 0, sql: 0x455d15680, psql: 0x45bd0c8c8, user: 92/XXXX
    ksuxds FALSE at location: 0
    service name: SYS$USERS
    client details:
      O/S info: user: idol, term: unknown, ospid: 1234
      machine:     program: JDBC Thin Client
      application name: JDBC Thin Client, hash value=2546894660
    Current Wait Stack:
     0: waiting for 'cursor: pin S wait on X'
        idn=de12d336, value=5cf00000000, where|sleeps=572cf03e3
        wait_id=56059196 seq_num=26772 snap_id=1
        wait times: snap=0.001772 sec, exc=0.001772 sec, total=0.001772 sec
        wait times: max=0.010000 sec
        wait counts: calls=1 os=1
        in_wait=1 iflags=0x5a2
    There is at least one session blocking this session.
    Dumping one blocker:
      inst: 1, sid: 1487, ser: 5556

其实这里已经指出“inst: 1, sid: 1487, ser: 5556”,表示阻塞的会话是sid,serial#为1487,5556。那么如果这里没有这个信息,那么我们从哪里去寻找blocker?注意前面一段信息:idn=de12d336(对cursor来说其实是sql的hash value的低4字节), value=5cf00000000, where|sleeps=572cf03e3,这里idn表示mutex的identifier,即mutex的标识,value的高8字节(64位系统是高8字节,而32位系统是高4位字节)是SID,0x5cf即10进制的1487。

而在稍后面部分,可以看到:

      KGX Atomic Operation Log 0x455507d20
       Mutex 0x3ff3c3d28(1487, 0) idn de12d336 oper GET_SHRD
       Cursor Pin uid 1471 efd 0 whr 5 slp 13085
       opr=2 pso=0x454d03f00 flg=0
       pcs=0x3ff3c3ca8 nxt=0x41720aca0 flg=35 cld=0 hd=0x455d0dda8 par=0x3ff3c4650
       ct=0 hsh=0 unp=(nil) unn=0 hvl=ff3c4448 nhv=1 ses=0x45181f760
       hep=0x3ff3c3d28 flg=80 ld=1 ob=0x3ff3c3868 ptr=0x412040780 fex=0x41203faf0

这里有Mutex请求的更详细的信息,Mutex 0x3ff3c3d28(1487, 0) idn de12d336 oper GET_SHRD表明这个请求是以SHARE方式请求,但是Mutex被1487这个会话持有。idn与前面idn相匹配。Cursor Pin uid 1471 efd 0 whr 5 slp 13085 这里表明Mutex的类型是Cursor Pin,正是前面提到的Mutex的一种。uid 1471表明是当前请求的SID为1471。

'cursor: pin S wait on X'等待类似于以往的library cache pin等待,表示需要将cursor pin在内存中。这个等待出现,表明有会话正在修改这个cursor,比如正在执行解析,生成执行计划等。

接下来我们看SID=1487的会话正在等待什么,或者说是在执行什么操作,查找'(session) sid: 1487':

    (session) sid: 1487 ser: 5556 trans: (nil), creator: 0x450d514f0
              flags: (0x45) USR/- flags_idl: (0x1) BSY/-/-/-/-/-
              flags2: (0x40008) -/-
              DID: , short-term DID: 
              txn branch: (nil)
              oct: 3, prv: 0, sql: 0x455d15680, psql: 0x45bd0c8c8, user: 92/XXXXX
    ksuxds FALSE at location: 0
    service name: SYS$USERS
    client details:
      O/S info: user: idol, term: unknown, ospid: 1234
      machine:       program: JDBC Thin Client
      application name: JDBC Thin Client, hash value=2546894660
    Current Wait Stack:
     0: waiting for 'cursor: mutex X'
        idn=5336, value=1, where|sleeps=3385a5f33
        wait_id=500096450 seq_num=64401 snap_id=1
        wait times: snap=0.000000 sec, exc=0.000000 sec, total=0.000000 sec
        wait times: max=0.000000 sec
        wait counts: calls=0 os=0
        in_wait=0 iflags=0x15e0

可以看到,这个会话正在等待'cursor: mutex X'。不幸的是,这一次没有直接表明holder是谁了。idn=5336, value=1, where|sleeps=3385a5f33,这里value=1,很简单,但是表明的意义却不简单,这表明什么?低8字节为1,表明这个Mutex是被共享持有,而高8字节为0,这是共享持有的Mutex的正常状况,Mutex可能被多个会话共享持有,因此只有引用计数,而不会有持有的SID。这里1就是引用计数,表示被1个会话持有。只有在会话获取Mutex的过程中,才会暂时性的将Mutex的SID设为正在获取的SID,而一旦完成获取,就将Mutex中的SID即value的高8字节部分清0。

继续往后查看:

      KGX Atomic Operation Log 0x4543ce390
       Mutex 0x3ff3c3d28(1487, 0) idn de12d336 oper EXCL
       Cursor Pin uid 1487 efd 0 whr 1 slp 0
       opr=3 pso=0x454daaa50 flg=0
       pcs=0x3ff3c3ca8 nxt=0x41720aca0 flg=35 cld=0 hd=0x455d0dda8 par=0x3ff3c4650
       ct=0 hsh=0 unp=(nil) unn=0 hvl=ff3c4448 nhv=1 ses=0x45181f760
       hep=0x3ff3c3d28 flg=80 ld=1 ob=0x3ff3c3868 ptr=0x412040780 fex=0x41203faf0

上面一段信息表明,正是1487这个会话以EXCLUSIVE方式持有idn=de12d336的这个mutex,这正是1471会话正需要以SHARE方式获取的Mutex。

      KGX Atomic Operation Log 0x45a2b7908
       Mutex 0x44b764498(0, 1) idn 7fff00005336 oper GET_EXCL
       Cursor Stat uid 1487 efd 8 whr 3 slp 28652
       oper=OPERATION_DEFAULT pt1=(nil) pt2=(nil) pt3=(nil)
       pt4=(nil) u41=0 stt=0

上面一段信息表明,1487这个会话正在以EXCLUSIVE请求的mutex是idn=7fff00005336,Mutex类型是Cursor Stat。Cursor Stat类型的EXCLUSIVE方式请求通常是需要更新cursor的统计信息。而以SHARE方式请求通常是查询cursor的统计信息,比如说查询v$sqlstat视图。注意这里GET_EXCL表示正在以EXCLUSIVE方式请求获取(持有)mutex,而EXCL表示已经以EXCLUSIVE方式持有mutex。延伸开来,GET_SHRD表示请求SHARE方式的mutex,SHRD表示以SHARE方式持有mutex。REL_SHRD表示正在释放SHARE方式持有的mutex,也就是递减引用计数。

那么,接下来我们就要寻找是哪个会话持有了idn=7fff00005336的mutex。查找'idn 7fff00005336 oper‘,找到如下内容:

      KGX Atomic Operation Log 0x45955b2f0
       Mutex 0x44b764498(0, 1) idn 7fff00005336 oper SHRD
       Cursor Stat uid 1497 efd 7 whr 6 slp 0
       oper=OPERATION_DEFAULT pt1=(nil) pt2=(nil) pt3=(nil)
       pt4=(nil) u41=0 stt=0

可以看到的确有会话(1497)正在以共享方式持有idn=7fff00005336的mutex。往前查看,可以看到会话信息:

    (session) sid: 1497 ser: 29349 trans: (nil), creator: 0x45cccc3f0
              flags: (0x51) USR/- flags_idl: (0x1) BSY/-/-/-/-/-
              flags2: (0x408) -/-
              DID: , short-term DID: 
              txn branch: (nil)
              oct: 0, prv: 0, sql: (nil), psql: (nil), user: 0/SYS
    ksuxds FALSE at location: 0
    service name: SYS$BACKGROUND
    Current Wait Stack:
      Not in wait; last wait ended 14005 min 23 sec ago 
    Wait State:
      auto_close=0 flags=0x21 boundary=(nil)/-1
    Session Wait History:
     0: waited for 'db file sequential read'
        file#=2, block#=1952c, blocks=1
        wait_id=1171 seq_num=1174 snap_id=1
        wait times: snap=0.018420 sec, exc=0.018420 sec, total=0.018420 sec
        wait times: max=infinite
        wait counts: calls=0 os=0
        occurred after 0.000034 sec of elapsed time

再往前可以找到进程信息:

PROCESS 38:
  ----------------------------------------
  SO: 0x45cccc3f0, type: 2, owner: (nil), flag: INIT/-/-/0x00 if: 0x3 c: 0x3
   proc=0x45cccc3f0, name=process, file=ksu.h LINE:10706, pg=0
  (process) Oracle pid:38, ser:76, calls cur/top: 0x440d0ae48/0x43c5e5f88
            flags : (0x2) SYSTEM
            flags2: (0x30),  flags3: (0x0) 
            int error: 0, call error: 0, sess error: 0, txn error 0
  ksudlp FALSE at location: 0
  (post info) last post received: 0 0 42
              last post received-location: ksv2.h LINE:1603 ID:ksvpst: run
              last process to post me: 450d4b4f0 1 2
              last post sent: 0 0 151
              last post sent-location: kcrf.h LINE:3095 ID:kcrfw_redo_gen: wake LGWR after redo copy
              last process posted by me: 45ccbe3f0 1 6
    (latch info) wait_event=0 bits=0
    Process Group: DEFAULT, pseudo proc: 0x4510353e0
    O/S info: user: oracle, term: UNKNOWN, ospid: 13549 
    OSD pid info: Unix process pid: 13549, image: oracle@xxxxx (m001)

可以看到m001这个进程持有了mutex。但是会话没有在执行任何sql语句,而从会话的等待来看,last wait ended 14005 min 23 sec ago ,这个是不正常的,m001进程应该是出现了异常。

m001进程是什么,这是mmon的slave进程,其中一个作用就是执行awr snapshot。很显然这个进程要访问v$sqlstat,是需要持有Cursor Stat类型的mutex的。

至此就找到了所有等待的root holder。不过把这个结果告诉维护这个库的朋友时,告知服务器之前“死”了,已经重启了。重启后检查数据库,发现在故障期间很长一段时间内的确是没有产生awr snapshot。至于m001进程出了什么异常,为什么会出现异常,本文就不再详细讨论。实际上服务器重启之后也没有继续跟踪这一问题。

mutex从设计上来说,与latch相比有着很明显的优点,只是作为新生事务(相对于目前仍然在广泛使用的10g来说), 存在BUG的可能性比较大。深入了解mutex,可以帮助我们更好地诊断mutex引起的相关问题。

在一个表上建索引时,报ORA-01410错误,我们查询这个表来重现这个错误:

Connected to:
Oracle9i Enterprise Edition Release 9.2.0.6.0 - 64bit Production
With the Partitioning option
JServer Release 9.2.0.6.0 - Production

SQL> set timing on
SQL> set time on
14:20:03 SQL> select /*+ full(a) no_index(a) */ count(*) from crm.cust_order a; 
select /*+ full(a) no_index(a) */ count(*) from crm.cust_order a
                                                    *
ERROR at line 1:
ORA-01410: invalid ROWID

ORA-01410错误通常见于通过索引访问表,而索引或表由逻辑上的损坏。而这里显示没有通过索引访问表?那问题出在哪里呢?在这种情况下,这个错误与ORA-08103极其类似,参照《记一次ORA-8103错误的处理》

14:27:00 SQL> alter session set max_dump_file_size=unlimited;

Session altered.

Elapsed: 00:00:00.01
14:27:18 SQL> alter session set db_file_multiblock_read_count=1;

Session altered.

Elapsed: 00:00:00.00
14:27:18 SQL> alter session set events 'immediate trace name trace_buffer_on level 1048576';

Session altered.

Elapsed: 00:00:00.00
14:27:18 SQL> alter session set events '10200 trace name context forever, level 1';

Session altered.

Elapsed: 00:00:00.00
14:27:18 SQL> select /*+ full(a) no_index(a) */ count(*) from crm.cust_order a; 
ERROR at line 1:
ORA-01410: invalid ROWID


Elapsed: 00:05:50.82
14:33:09 SQL> 14:33:09 SQL> alter session set events 'immediate trace name trace_buffer_off';

Session altered.

在trace文件的最后,我们可以看到:

Consistent read started for block 10 : 2489c394
  env: (scn: 0x0a0d.690ff414  xid: 0x0000.000.00000000  uba: 0x00000000.0000.00  statement num=0  parent xid: xid: 0x0000.000.000000
00  scn: 0x0000.00000000 0sch: scn: 0x0000.00000000)

这里只有"start“,而没有finish,表明在读2489c394这个块出了问题。
用ODU工具的rdba查看文件号和坏号:

ODU> rdba 2489c394

  rdba   : 0x2489c394=613008276
  rfile# : 146
  block# : 639892

通过"alter sytem dump datafile 146 block 639892”命令发现块中的object_id与CUST_ORDER表的data object id不同,看起来这就是问题所在(此处不再列出数据)。
看起来有坏块了。不过这个库是个查询库,把表TRUNCATE之后重新从生产库同步过来,发现问题仍然存在,甚至把表DROP之后重建也是如此,均是发生在146/639892这个块上。

而TRUNCATE/DROP表都不能解决问题,显然这个块还在内存中,看起来需要刷新buffer cache了:

14:37:07 SQL> alter session set events 'immediate trace name flush_cache level 1';
Session altered.

刷新buffer cache后,问题解决。

总结:这个问题,与ORA-8103类似,都是出现了逻辑坏块,只不过这次的坏块是发生在内存中的块。至于坏块是怎么进入到内存中,为什么在重建表后还在内存中,这就是个谜了,或者是ORACLE的BUG,或者跟用的同步软件DSG有关。在这个案例中,块的object_id与段的实际的data object id不一致。而object_id不一致有时也会报ORA-600错误。

说起来汗颜,我这个BLOG主要写Oracle相关的文章,也附带写点UNIX,可惜从来没正经写过UNIX方面的东西。毕竟不是专业的SA,水平不够恐怕误导读者朋友。这次的故障,主要是从OS层进行处理的,稍微算是沾上一点UNIX的边。闲话少扯了,说正事吧。

事情的起因,是系统的最终用户反映某些查询功能比较慢。简单地看了一下主机的负载以及数据库的性能状况,没发现什么异常,甚至可以说系统相当地轻闲。

那问题出在哪?我首先观察到内存的使用率相当地高,达到99%。但是从操作上看,速度还没受到影响。不过很快想到,这个系统某些模块,用了短连接,难道是监听太慢引起的?这个库启了6个监听(详见《一切皆有可能》),分别TNSPING这几个监听,有个别监听非常慢,重启监听后,查询功能比较慢的问题得到解决。

不过之前观察到的内存的异常使用引起了我极大的注意。这套系统,平时一般都会有几十G的空闲内存,不会达到这么高的。第一反应是用ipcs命令检查一下共享内存,发现有一个异常的共享内存段,占了60多G。

[oracle@hostname%/oracle]ipcs -ma
IPC status from /dev/kmem as of Mon Dec  7 10:58:53 2009
T         ID     KEY        MODE        OWNER     GROUP   CREATOR    CGROUP NATTCH      SEGSZ  CPID  LPID   ATIME    DTIME    CTIME 
Shared Memory:
m          0 0x41180809 --rw-rw-rw-      root      root      root      root      0        348  2725  2725  2:38:57  2:38:57  2:38:50
m          1 0x4e0c0002 --rw-rw-rw-      root      root      root      root      2      61760  2725  2727 12:27:19 18:19:39  2:38:50
m          2 0x411c0de1 --rw-rw-rw-      root      root      root      root      2       8192  2725  2727 12:27:19  2:38:50  2:38:50
m          3 0x00a5c581 --rw-------     sfmdb     users     sfmdb     users     11   10469376  3362  3398  2:39:38  2:39:39  2:39:38
m          4 0x4118043d --rw-------      root      root      root      root      1       4096  3410  4745  2:40:12 no-entry  2:40:12
m          5 0x06347849 --rw-rw-rw-      root      root      root      root      1      65544  3535  6722 17:53:03 17:53:03  2:39:47
m    1015814 0x0c6629c9 --rw-r-----      root       dba      root       dba      0   35921048  6722  6722 17:53:03 no-entry 17:53:03
m     819207 0x491002d0 --rw-r--r--      root      root      root      root      0      22908  3674  3674  2:39:54  2:39:54  2:39:54
m    5472264 0x00000000 D-rw-r-----    oracle       dba    oracle       dba      6 66640334848  5508 23604 17:58:00 17:58:00 17:58:00
m   95387657 0x0000cace --rw-rw-rw-      root       sys      root       sys      0          2 21306 21306 20:24:33 20:24:33 20:24:29
m   35520522 0xa57bccf8 --rw-r-----    oracle       dba    oracle       dba  12231 66640334848  3231 26942 10:58:53 10:58:53 18:10:36

ID为"5472264"的共享内存段就是异常的共享内存段。
为什么会出现这种情况?数据库可以确定是被重启过,询问客户这套系统的DBA,的确是在头一天出现了异常然后进行了重启。至于出现了什么样的异常,为什么要重启,这里不再深入。本文只讨论怎么样来清除这个异常的共享内存段。

由于这个内存段的NTATTCH(number of attach)为6,在HP-UX下是清理不掉的:

[oracle@hostname%/oracle]ipcrm -m 5472264
ipcrm: shmid(5472264): not found

这是由于还有进程attach(理解为连接吧)到这个共享内存段上。只要找到这个进程被KILL之,就会解决问题。一种简单的方法是使用lsof来找到这些进程:

[oracle@hostname%/oracle]lsof | egrep "COMMAND|5472264"

不过简单的方法,不一定效率就高。这个系统光oracle server process就有5000个以上,lsof实在很慢。所以运行几分钟就直接放弃(因为以前在这套系统上运行过lsof命令,知道要输出完结果时间比较“漫长”)。

OK, 手工找一下吧。从上面的ipcs输出的CTIME字段看到,正常的共享内存段是18:10左右创建的,而异常的是17:58左右创建的,那么attach到这个异常共享内存段的进程应该是在18点之前创建,而在17:58左右。首先使用"ps -ef | grep defunct“,没有发现僵死进程。然后根据这样的条件,并且经过一系列筛选,得到下面的结果:

[oracle@hostname%/oracle]ps -ef | grep oraclesidname | grep "17:" | grep -v "18:17" | grep -v "11:17"
  oracle 22586     1  1 07:17:43 ?         0:31 oraclesidname (LOCAL=NO)
  oracle 28403     1  0 09:17:38 ?         0:02 oraclesidname (LOCAL=NO)
  oracle 22618     1  0 07:17:59 ?         0:00 oraclesidname (LOCAL=NO)
  oracle  7539     1  0 08:17:42 ?         0:10 oraclesidname (LOCAL=NO)
  oracle  7419     1  0 08:17:05 ?         0:00 oraclesidname (LOCAL=NO)
  oracle 22580     1  0 07:17:42 ?         0:36 oraclesidname (LOCAL=NO)
  oracle  7421     1  0 08:17:06 ?         0:06 oraclesidname (LOCAL=NO)
  oracle  7537     1  0 08:17:42 ?         0:02 oraclesidname (LOCAL=NO)
  oracle  7535     1  0 08:17:41 ?         0:00 oraclesidname (LOCAL=NO)
  oracle 21395     1  0 17:56:49 ?         0:01 oraclesidname (LOCAL=NO)
  oracle 22616     1  0 07:17:59 ?         0:00 oraclesidname (LOCAL=NO)
  oracle 20786     1  0 17:54:24 ?         0:10 oraclesidname (LOCAL=NO)
  oracle 22614     1  0 07:17:58 ?         0:00 oraclesidname (LOCAL=NO)
  oracle  7423     1  0 08:17:06 ?         0:18 oraclesidname (LOCAL=NO)

看上去进程号为21395和20786的进程,正好满足前面提到的条件。KILL这两个进程,检查共享内存段,发现这个异常的共享内存段自动被清除。再检查内存的使用,内存的使用率也大幅下降,回到正常状态。

今天也算是幸运的,在没有监控系统的情况下,人为的较早发现了这个问题,避免了全系统范围内的系统问题。如果没有及时发现这个问题,内存的使用一上去,开始大量使用交换页,那就头疼多了。