升级不正确导致ORA-27468错误

客户10.2.0.4环境告警日志出现ORA-27468错误。
详细错误信息为:

Errors IN file /u01/app/oracle/admin/orcl/bdump/orcl1_j000_18724.trc:
ORA-12012: error ON auto EXECUTE OF job 42791
ORA-27468: "EXFSYS.RLM$EVTCLEANUP" IS locked BY another process

导致这个错误的原因在于升级时没有执行catupgrd.sql,而是执行了建库的部分脚本如catalog.sql和catproc.sql,这导致只有CATALOG视图和系统的PACKAGE以及TYPE的版本更新到10.2.0.4,而其他数据库中组件的版本并没有升级,仍然是10.2.0.1。
在MOS文档ORA-12012 ORA-27468: “SYS.PURGE_LOG” is Locked by Another Process [ID 751884.1]中介绍了这个错误,这个问题可能发生在10.2.0.2到10.2.0.5之间,解决问题的方法很简单,在闲时执行catupgrd.sql,完成升级组件的后续操作既可。

Posted in BUG | Tagged , , , | Leave a comment

ORACLE PLSQL语言参考

无论是开发DBA还是管理DBA,PL/SQL和SQL一样都是基本功之一。
这篇文档介绍了PL/SQL的语法和功能。和其他文档不太一样,我的PL/SQL知识并非来自Oracle的官方文档,因为我认为Oracle的文档对于学习语言并不合适,相反Oracle的文档作为一个参考书或是说一个语法的字典是再合适不过了。
虽然个人观点是这篇文档用来学习PL/SQL或者说进行PL/SQL知识的扫盲并不合适,但并不妨碍有兴趣的朋友来看,这篇文档的在线阅读地址为:http://www.oracle.com/pls/db112/to_toc?pathname=appdev.112%2Fe25519%2Ftoc.htm&remark=portal+%28Books%29

Posted in BOOKS | Leave a comment

20120630 ACOUG活动

今天是ACOUG活动的日子,也是上半年的最后一天。
参加ACOUG的人很多,现场估计超过了100人,而且今天到会的ACE也很多,除了Eygle、Kamus、崔华和刚晋升为ACE的侯圣文之外,还有从成都赶过来的老熊。老熊虽然是第一次见面,此前打交道的次数也不多,但是有种一见如故的感觉,可能和老熊直爽的性格有关。
今天的第一个主题是杨海朝带来的《MySql的那点事儿》。杨海朝是新浪的首席DBA负责新浪微博的后台数据库,他带来的MySql方面的演讲介绍了如何利用mysql处理海量增长的大数据,以及如何满足前端应用的高可用需求。
第二个是Kamus带来的11g新特性。在10g已经推出了正常服务期,12c马上就要推出的今天,仍然有很多DBA对于11g的新特性不是很了解。Kamus的演讲应该算是一个新特性的普及,这次的内容是关于分区和高可用特性方面的新特性。
最后是崔华带来的SQL优化方面的主题,这次的内容是统计信息。崔华的演讲贯彻了他一向的风格,包含了大量的深入信息,并给出了很多Oracle内部算法和公式,而这些公式大部分来自他深入的研究,在其他的地方是无法看到的。

Posted in NEWS | Leave a comment

对象相关开发手册总结

从这篇文档中学到了不少语法和功能。
虽然对于TYPE以及COLLECTION类型经常使用,但是涉及到把这些类型作为表的存储结构接触的就少多了,而如果说在加上继承等告警特性,平常使用的就更少了。
在Oracle使用对象一般而言是为了简化某些工作,而把所有面向对象的语法和功能都搬到数据库中不太现实,而且一旦TYPE被应用到表结构中,就很难再进行改变,因此数据库中使用TYPE有一定的局限性。
不过对于一些语法和功能还是有必要知道的,或者至少在碰到问题时,应该知道查看那篇文档。

Posted in BOOKS | Leave a comment

HASH分区新增分区对索引状态的影响

一直认为Oracle对于所有分区的操作都是一样的,只有数据的改变才会导致分区状态的失效,没想到HASH分区的实现方式并不相同。
HASH分区表增加新的分区的一点研究:http://yangtingkun.itpub.net/post/468/195510
看一个范围分区SPLIT的例子:

SQL> CREATE TABLE T_PART 
  2  (ID NUMBER, NAME VARCHAR2(30))
  3  PARTITION BY RANGE (ID)
  4  (PARTITION P1 VALUES LESS THAN (10), 
  5  PARTITION PMAX VALUES LESS THAN (MAXVALUE));
TABLE created.
SQL> INSERT INTO T_PART
  2  SELECT ROWNUM, TNAME  
  3  FROM TAB;
12 ROWS created.
SQL> CREATE INDEX IND_T_PART_ID ON T_PART(ID) LOCAL;
INDEX created.
SQL> SELECT INDEX_NAME, PARTITION_NAME, STATUS 
  2  FROM USER_IND_PARTITIONS
  3  WHERE INDEX_NAME = 'IND_T_PART_ID';
INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
IND_T_PART_ID                  P1                             USABLE
IND_T_PART_ID                  PMAX                           USABLE
SQL> SELECT COUNT(*) FROM T_PART PARTITION (PMAX);
  COUNT(*)
----------
         3
SQL> ALTER TABLE T_PART SPLIT PARTITION PMAX AT (20)
  2  INTO (PARTITION P2, PARTITION P3);
TABLE altered.
SQL> SELECT INDEX_NAME, PARTITION_NAME, STATUS 
  2  FROM USER_IND_PARTITIONS
  3  WHERE INDEX_NAME = 'IND_T_PART_ID';
INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
IND_T_PART_ID                  P2                             USABLE
IND_T_PART_ID                  P3                             USABLE
IND_T_PART_ID                  P1                             USABLE

可以看到,对于范围分区而言,即使是SPLIT包含数据的分区,只要没有真正导致数据发生变化,就不会导致索引的失效。这里将PMAX分区SPLIT成P2和P3两个分区,其中PMAX中的所有数据都进入P2分区,而P3分区为空,这种情况下没有数据的改变,因此所有分区索引的状态都不会变为UNUSABLE。
但是HASH分区的ADD PARTITION并没有遵守这个规则,事实上对于每次ADD分区,都会导致一个分区的数据发生分裂,而分裂的结果不管原分区的数据是否发生变化,都会导致原分区索引状态变为UNUSABLE,至于新增分区的索引状态,则取决于是否有数据的改变。

SQL> CREATE TABLE T_HASH 
  2  (ID NUMBER)
  3  PARTITION BY HASH (ID)
  4  (PARTITION P1,
  5  PARTITION P2, 
  6  PARTITION P3, 
  7  PARTITION P4);
TABLE created.
SQL> CREATE INDEX IND_T_HASH_ID ON T_HASH(ID) LOCAL;
INDEX created.
SQL> INSERT INTO T_HASH SELECT ROWNUM FROM TAB;
12 ROWS created.
SQL> COMMIT;
Commit complete.
SQL> SELECT INDEX_NAME, PARTITION_NAME, STATUS
  2  FROM USER_IND_PARTITIONS
  3  WHERE INDEX_NAME = 'IND_T_HASH_ID';
INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
IND_T_HASH_ID                  P1                             USABLE
IND_T_HASH_ID                  P2                             USABLE
IND_T_HASH_ID                  P3                             USABLE
IND_T_HASH_ID                  P4                             USABLE
SQL> SELECT * FROM T_HASH PARTITION (P1);
        ID
----------
         6
        11
SQL> SELECT * FROM T_HASH PARTITION (P2);
        ID
----------
         9
        10
        12
SQL> SELECT * FROM T_HASH PARTITION (P3);
        ID
----------
         2
         5
         8
SQL> SELECT * FROM T_HASH PARTITION (P4);
        ID
----------
         1
         3
         4
         7

下面新增一个PARTITION P5:

SQL> ALTER TABLE T_HASH ADD PARTITION P5;
TABLE altered.
SQL> SELECT * FROM T_HASH PARTITION (P5);
no ROWS selected
SQL> SELECT INDEX_NAME, PARTITION_NAME, STATUS
  2  FROM USER_IND_PARTITIONS
  3  WHERE INDEX_NAME = 'IND_T_HASH_ID';
INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
IND_T_HASH_ID                  P5                             USABLE
IND_T_HASH_ID                  P1                             UNUSABLE
IND_T_HASH_ID                  P2                             USABLE
IND_T_HASH_ID                  P3                             USABLE
IND_T_HASH_ID                  P4                             USABLE

新增的PARTITION P5中并没有任何的数据,也就是说没有任何的数据从P1迁移到P5中,但是查询分区索引的状态发现,P1对应的分区索引状态已经变为UNUSABLE。这和范围分区的处理方式完全不同。而P5分区由于没有任何数据,因此分区状态是USABLE。

SQL> ALTER TABLE T_HASH ADD PARTITION P6;
TABLE altered.
SQL> SELECT INDEX_NAME, PARTITION_NAME, STATUS
  2  FROM USER_IND_PARTITIONS 
  3  WHERE INDEX_NAME = 'IND_T_HASH_ID';
INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
IND_T_HASH_ID                  P5                             USABLE
IND_T_HASH_ID                  P6                             UNUSABLE
IND_T_HASH_ID                  P1                             UNUSABLE
IND_T_HASH_ID                  P2                             UNUSABLE
IND_T_HASH_ID                  P3                             USABLE
IND_T_HASH_ID                  P4                             USABLE
6 ROWS selected.
SQL> DELETE T_HASH WHERE ID = 5;        
1 ROW deleted.
SQL> COMMIT;
Commit complete.
SQL> ALTER TABLE T_HASH ADD PARTITION P7;
TABLE altered.
SQL> SELECT INDEX_NAME, PARTITION_NAME, STATUS 
  2  FROM USER_IND_PARTITIONS 
  3  WHERE INDEX_NAME = 'IND_T_HASH_ID';
INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
IND_T_HASH_ID                  P5                             USABLE
IND_T_HASH_ID                  P6                             UNUSABLE
IND_T_HASH_ID                  P7                             UNUSABLE
IND_T_HASH_ID                  P1                             UNUSABLE
IND_T_HASH_ID                  P2                             UNUSABLE
IND_T_HASH_ID                  P3                             UNUSABLE
IND_T_HASH_ID                  P4                             USABLE
7 ROWS selected.
SQL> SELECT * FROM T_HASH PARTITION (P3);
no ROWS selected
SQL> SELECT * FROM T_HASH PARTITION (P7);
        ID
----------
         2
         8

为了更好的说明这个问题,在增加PARTITION P7之前,删除了ID为5的记录,这是增加分区后可以发现,原有的P3已经不包含任何的数据,全部的记录都进入到新增的P7分区,但是无论是P3还是P7,状态都是UNUSABLE。这证明了前面提到的,只要是新增HASH分区,就会导致源分区索引状态变为UNUSABLE,除非是一种情况:源分区本身就没有数据:

SQL> ALTER TABLE T_HASH ADD PARTITION P8;
TABLE altered.
SQL> SELECT INDEX_NAME, PARTITION_NAME, STATUS                          
  2  FROM USER_IND_PARTITIONS 
  3  WHERE INDEX_NAME = 'IND_T_HASH_ID';
INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
IND_T_HASH_ID                  P5                             USABLE
IND_T_HASH_ID                  P6                             UNUSABLE
IND_T_HASH_ID                  P7                             UNUSABLE
IND_T_HASH_ID                  P1                             UNUSABLE
IND_T_HASH_ID                  P2                             UNUSABLE
IND_T_HASH_ID                  P3                             UNUSABLE
IND_T_HASH_ID                  P4                             USABLE
IND_T_HASH_ID                  P8                             USABLE
8 ROWS selected.

事实上,对于HASH分区的ADD PARTITION操作,Oracle基本上还是秉承了没有数据变化就不会导致索引失效的思路。唯一的差别在于,对于源分区包含记录的情况,Oracle并没有最后去验证,是否真的发生了数据的迁移。

Posted in ORACLE | Tagged , , , | Leave a comment

单个分区索引失效导致绑定变量查询无法使用索引

一个客户碰到的问题,由于分区维护操作,导致个别分区对应的索引处于UNUSABLE状态,最终导致基于绑定变量的查询无法利用索引。
通过一个具体的例子来说明这个问题:

SQL> CREATE TABLE t_part 
2 (id NUMBER, 
3 name varchar2(30)) 
4 partition BY range (id)
5 (partition p1 VALUES less than (10), 
6 partition p2 VALUES less than (20), 
7 partition pmax VALUES less than (maxvalue));
TABLE created.
SQL> CREATE INDEX ind_t_part_id ON t_part(id) LOCAL;
INDEX created.
SQL> INSERT INTO t_part 
2 SELECT rownum, object_name
3 FROM user_objects;
94 ROWS created.
SQL> commit;
Commit complete.
SQL> EXEC dbms_stats.gather_table_stats(USER, 'T_PART', cascade => TRUE)
PL/SQL PROCEDURE successfully completed.
SQL> SELECT index_name, partition_name, STATUS 
2 FROM user_ind_partitions
3 WHERE index_name = 'IND_T_PART_ID';
INDEX_NAME PARTITION_NAME STATUS
------------------------------ ------------------------------ --------
IND_T_PART_ID P1 USABLE
IND_T_PART_ID P2 USABLE
IND_T_PART_ID PMAX USABLE

创建分区表后,分别采用硬编码和绑定变量的方式进行查询:

SQL> var v_id NUMBER
SQL> EXEC :v_id := 5
PL/SQL PROCEDURE successfully completed.
SQL> SET autot ON EXP
SQL> SELECT * FROM t_part WHERE id = 5;
        ID NAME
---------- ------------------------------
         5 WRH$_ACTIVE_SESSION_HISTORY
 
Execution Plan
----------------------------------------------------------
Plan hash VALUE: 4087175928
--------------------------------------------------------------------------------------------
|Id|Operation                          |Name         |ROWS|Bytes|Cost|TIME    |Pstart|Pstop|
--------------------------------------------------------------------------------------------
| 0|SELECT STATEMENT                   |             |   1|   31|   2|00:00:01|      |     |
| 1| PARTITION RANGE SINGLE            |             |   1|   31|   2|00:00:01|    1 |    1|
| 2|  TABLE ACCESS BY LOCAL INDEX ROWID|T_PART       |   1|   31|   2|00:00:01|    1 |    1|
|*3|   INDEX RANGE SCAN                |IND_T_PART_ID|   1|     |   1|00:00:01|    1 |    1|
--------------------------------------------------------------------------------------------
Predicate Information (IDENTIFIED BY operation id):
---------------------------------------------------
   3 - access("ID"=5)
SQL> SELECT * FROM t_part WHERE id = :v_id;
        ID NAME
---------- ------------------------------
         5 WRH$_ACTIVE_SESSION_HISTORY
 
Execution Plan
----------------------------------------------------------
Plan hash VALUE: 2089936139
--------------------------------------------------------------------------------------------
|Id|Operation                          |Name         |ROWS|Bytes|Cost|TIME    |Pstart|Pstop|
--------------------------------------------------------------------------------------------
| 0|SELECT STATEMENT                   |             |   1|   17|   2|00:00:01|      |     |
| 1| PARTITION RANGE SINGLE            |             |   1|   17|   2|00:00:01|  KEY |  KEY|
| 2|  TABLE ACCESS BY LOCAL INDEX ROWID|T_PART       |   1|   17|   2|00:00:01|  KEY |  KEY|
|*3|   INDEX RANGE SCAN                |IND_T_PART_ID|   1|     |   1|00:00:01|  KEY |  KEY|
--------------------------------------------------------------------------------------------
Predicate Information (IDENTIFIED BY operation id):
---------------------------------------------------
   3 - access("ID"=TO_NUMBER(:V_ID))

无论采用那种方式,Oracle都会选择分区索引扫描的执行计划。
下面MOVE一个查询并不会访问的分区,使其索引状态变为UNUSABLE:

SQL> ALTER TABLE t_part move partition p2;
TABLE altered.
SQL> SET autot off
SQL> SELECT index_name, partition_name, STATUS
  2  FROM user_ind_partitions
  3  WHERE index_name = 'IND_T_PART_ID';
INDEX_NAME                     PARTITION_NAME                 STATUS
------------------------------ ------------------------------ --------
IND_T_PART_ID                  P1                             USABLE
IND_T_PART_ID                  P2                             UNUSABLE
IND_T_PART_ID                  PMAX                           USABLE
SQL> SET autot ON EXP
SQL> SELECT * FROM t_part WHERE id = 5;
        ID NAME
---------- ------------------------------
         5 WRH$_ACTIVE_SESSION_HISTORY
 
Execution Plan
----------------------------------------------------------
Plan hash VALUE: 4087175928
--------------------------------------------------------------------------------------------
|Id|Operation                          |Name         |ROWS|Bytes|Cost|TIME    |Pstart|Pstop|
--------------------------------------------------------------------------------------------
| 0|SELECT STATEMENT                   |             |   1|   31|   2|00:00:01|      |     |
| 1| PARTITION RANGE SINGLE            |             |   1|   31|   2|00:00:01|    1 |    1|
| 2|  TABLE ACCESS BY LOCAL INDEX ROWID|T_PART       |   1|   31|   2|00:00:01|    1 |    1|
|*3|   INDEX RANGE SCAN                |IND_T_PART_ID|   1|     |   1|00:00:01|    1 |    1|
--------------------------------------------------------------------------------------------
Predicate Information (IDENTIFIED BY operation id):
---------------------------------------------------
   3 - access("ID"=5)
SQL> SELECT * FROM t_part WHERE id = :v_id;
        ID NAME
---------- ------------------------------
         5 WRH$_ACTIVE_SESSION_HISTORY
 
Execution Plan
----------------------------------------------------------
Plan hash VALUE: 1818654859
--------------------------------------------------------------------------------------------
| Id| Operation              | Name   | ROWS | Bytes |Cost(%CPU)| TIME     | Pstart| Pstop |
--------------------------------------------------------------------------------------------
|  0| SELECT STATEMENT       |        |    1 |    17 |    2  (0)| 00:00:01 |       |       |
|  1|  PARTITION RANGE SINGLE|        |    1 |    17 |    2  (0)| 00:00:01 |   KEY |   KEY |
|* 2|   TABLE ACCESS FULL    | T_PART |    1 |    17 |    2  (0)| 00:00:01 |   KEY |   KEY |
--------------------------------------------------------------------------------------------
Predicate Information (IDENTIFIED BY operation id):
---------------------------------------------------
   2 - FILTER("ID"=TO_NUMBER(:V_ID))

可以看到,对应非绑定变量方式,Oracle是可以明确定位到要访问的分区,因此SQL执行计划不受影响,仍然是索引扫描。而对于绑定变量的方式则不同,由于这个执行计划对于任何一个输入值都要采用相同的计划,因此Oracle无法判断一个查询是否会访问分区索引UNUSABLE的分区,所以Oracle对于绑定变量的查询采用了单分区的全表扫描执行计划。
为了解决这个问题,除了REBUILD失效的分区外,还可以采用HINT的方式,强制Oracle选择索引扫描的执行计划:

SQL> SELECT /*+ index(t_part ind_t_part_id) */ * FROM t_part WHERE id = :v_id;
        ID NAME
---------- ------------------------------
         5 WRH$_ACTIVE_SESSION_HISTORY
 
Execution Plan
----------------------------------------------------------
Plan hash VALUE: 2089936139
--------------------------------------------------------------------------------------------
|Id|Operation                          |Name         |ROWS|Bytes|Cost|TIME    |Pstart|Pstop|
--------------------------------------------------------------------------------------------
| 0|SELECT STATEMENT                   |             |   1|   17|   2|00:00:01|      |     |
| 1| PARTITION RANGE SINGLE            |             |   1|   17|   2|00:00:01|  KEY |  KEY|
| 2|  TABLE ACCESS BY LOCAL INDEX ROWID|T_PART       |   1|   17|   2|00:00:01|  KEY |  KEY|
|*3|   INDEX RANGE SCAN                |IND_T_PART_ID|   1|     |   1|00:00:01|  KEY |  KEY|
--------------------------------------------------------------------------------------------
Predicate Information (IDENTIFIED BY operation id):
---------------------------------------------------
   3 - access("ID"=TO_NUMBER(:V_ID))
SQL> EXEC :v_id := 15
PL/SQL PROCEDURE successfully completed.
SQL> SELECT /*+ index(t_part ind_t_part_id) */ * FROM t_part WHERE id = :v_id;
SELECT /*+ index(t_part ind_t_part_id) */ * FROM t_part WHERE id = :v_id
*
ERROR at line 1:
ORA-01502: INDEX 'TEST.IND_T_PART_ID' OR partition OF such INDEX IS IN unusable state
 
SQL> SELECT * FROM t_part WHERE id = :v_id;
        ID NAME
---------- ------------------------------
        15 WRH$_ACTIVE_SESSION_HISTORY_PK
 
Execution Plan
----------------------------------------------------------
Plan hash VALUE: 1818654859
--------------------------------------------------------------------------------------------
| Id | Operation              | Name   | ROWS | Bytes |Cost(%CPU)| TIME     |Pstart| Pstop |
--------------------------------------------------------------------------------------------
|  0 | SELECT STATEMENT       |        |    1 |    17 |    2  (0)| 00:00:01 |      |       |
|  1 |  PARTITION RANGE SINGLE|        |    1 |    17 |    2  (0)| 00:00:01 |  KEY |   KEY |
|* 2 |   TABLE ACCESS FULL    | T_PART |    1 |    17 |    2  (0)| 00:00:01 |  KEY |   KEY |
--------------------------------------------------------------------------------------------
Predicate Information (IDENTIFIED BY operation id):
---------------------------------------------------
   2 - FILTER("ID"=TO_NUMBER(:V_ID))

虽然使用HINT可以让Oracle强制索引扫描,但是如果绑定变量的值指向失效的索引分区,则会导致执行报错。而默认的不使用HINT的语句则不会报错。

Posted in ORACLE | Tagged , , , , | Leave a comment

KFED-320错误

利用kfed的repair命令修复asm头块,碰到这个错误。
错误信息如下:

$kfed repair /dev/rhdisk3 
KFED-00320: Invalid block num1 = [3], num2 = [1], error = [type_kfbh]

导致这个错误的原因是由于当前磁盘组的AUSIZE不是默认值1M,而是4M。
对于磁盘组的头块损坏,不知道具体AUSIZE的值的情况,有两个方法,一是通过ASM的告警日志文件,找磁盘组创建时的参数信息:

SUCCESS: CREATE diskgroup oradata external redundancy disk
'/dev/rhdisk3',
'/dev/rhdisk4',
'/dev/rhdisk5',
'/dev/rhdisk6',
.
.
.
'/dev/rhdisk31',
'/dev/rhdisk32'
attribute 'au_size'='4M',
'compatible.asm'='11.2',
'compatible.rdbms'='11.2'
NOTE: diskgroup resource ora.ORADATA.dg IS online

另一个方法是直接查看备份块信息。由于不同AUSIZE的值,备份块位置不同,不过通过几次尝试就肯定可以找到,备份块的位置是POWER(2, N) – 2,对于4M的AUSIZE,则备份块的位置2046:

$kfed READ /dev/rhdisk3 blkn=2046|more
kfbh.endian:                          0 ; 0x000: 0x00
kfbh.hard:                          130 ; 0x001: 0x82
kfbh.type:                            1 ; 0x002: KFBTYP_DISKHEAD
kfbh.datfmt:                          1 ; 0x003: 0x01
kfbh.block.blk:                    1022 ; 0x004: blk=1022
kfbh.block.obj:              2147483648 ; 0x008: disk=0
kfbh.check:                  2398806058 ; 0x00c: 0x8efae02a
kfbh.fcn.base:                        0 ; 0x010: 0x00000000
kfbh.fcn.wrap:                        0 ; 0x014: 0x00000000
kfbh.spare1:                          0 ; 0x018: 0x00000000
kfbh.spare2:                          0 ; 0x01c: 0x00000000
kfdhdb.driver.provstr:         ORCLDISK ; 0x000: LENGTH=8
kfdhdb.driver.reserved[0]:            0 ; 0x008: 0x00000000
kfdhdb.driver.reserved[1]:            0 ; 0x00c: 0x00000000
kfdhdb.driver.reserved[2]:            0 ; 0x010: 0x00000000
kfdhdb.driver.reserved[3]:            0 ; 0x014: 0x00000000
kfdhdb.driver.reserved[4]:            0 ; 0x018: 0x00000000
kfdhdb.driver.reserved[5]:            0 ; 0x01c: 0x00000000
kfdhdb.compat:                186646528 ; 0x020: 0x0b200000
kfdhdb.dsknum:                        0 ; 0x024: 0x0000
kfdhdb.grptyp:                        1 ; 0x026: KFDGTP_EXTERNAL
kfdhdb.hdrsts:                        3 ; 0x027: KFDHDR_MEMBER
kfdhdb.dskname:            ORADATA_0000 ; 0x028: LENGTH=12
kfdhdb.grpname:                 ORADATA ; 0x048: LENGTH=7
kfdhdb.fgname:             ORADATA_0000 ; 0x068: LENGTH=12
kfdhdb.capname:                         ; 0x088: LENGTH=0
kfdhdb.crestmp.hi:             32970512 ; 0x0a8: HOUR=0x10 DAYS=0x18 MNTH=0x5 YEAR=0x7dc
kfdhdb.crestmp.lo:            941082624 ; 0x0ac: USEC=0x0 MSEC=0x1f2 SECS=0x1 MINS=0xe
kfdhdb.mntstmp.hi:             32971030 ; 0x0b0: HOUR=0x16 DAYS=0x8 MNTH=0x6 YEAR=0x7dc
kfdhdb.mntstmp.lo:             97022976 ; 0x0b4: USEC=0x0 MSEC=0x21d SECS=0x1c MINS=0x1
kfdhdb.secsize:                     512 ; 0x0b8: 0x0200
kfdhdb.blksize:                    4096 ; 0x0ba: 0x1000
kfdhdb.ausize:                  4194304 ; 0x0bc: 0x00400000
kfdhdb.mfact:                    454272 ; 0x0c0: 0x0006ee80
kfdhdb.dsksize:                   51200 ; 0x0c4: 0x0000c800
kfdhdb.pmcnt:                         2 ; 0x0c8: 0x00000002
kfdhdb.fstlocn:                       1 ; 0x0cc: 0x00000001
kfdhdb.altlocn:                       2 ; 0x0d0: 0x00000002
kfdhdb.f1b1locn:                      2 ; 0x0d4: 0x00000002
kfdhdb.redomirrors[0]:                0 ; 0x0d8: 0x0000
kfdhdb.redomirrors[1]:                0 ; 0x0da: 0x0000

如果找遍ASM都无法找到备份头块,那么你的版本是10.2.0.4及以下版本,Oracle没有在ASM保存头块的备份。KFED的REPAIR功能是11g才提供的,因此你根本不会碰到文章里面提到的错误,不过坏消息是没有一个简单的办法来恢复ASM磁盘头了。
确定ASM的AUSIZE后,通过下面的命令修复ASM:

$kfed repair /dev/rhdisk3 aus=4194304
Posted in ORACLE | Tagged , , , , | Leave a comment

设置全局死锁优先级

测试控制全局死锁的隐含参数_lm_dd_interval时,突然想到这个问题。
RAC全局死锁检测时间:https://yangtingkun.net/?p=955
Oracle的死锁判断是没有优先级的,也就是说,当两个或多个会话发生死锁的时候,无法指定牺牲哪个会话,而是由Oracle随机决定。
不过对于RAC环境而言,死锁的检查不在是内部的随机实现,Oracle通过隐含参数_lm_dd_interval来控制死锁的检测时间。更重要的是,对于RAC环境而言,Oracle允许不同实例设置不同的值。而不同实例的检测死锁间隔不同,就意味着优先级的出现。
如果实例1上设置该值为默认值60秒,而实例2设置为30秒,那么当发生死锁后,永远是实例2上先检测到死锁,也就是说,实例2上会话会被牺牲掉。
这是两个实例上设置该参数相同的情况,两个会话分别连接到两个实例,产生死锁。实例1上的会话1:

SQL> SELECT name FROM v$database;
NAME
---------
ORCL
SQL> SELECT instance_number, instance_name FROM v$instance;
INSTANCE_NUMBER INSTANCE_NAME
--------------- ----------------
              1 orcl1
SQL> SET sqlp 'I1S1> '
I1S1> SHOW parameter _lm
NAME                                 TYPE                                 VALUE
------------------------------------ ----------- ------------------------------
_lm_dd_interval                      INTEGER                                 30
I1S1> SET timing ON 
I1S1> UPDATE t_deadlock SET name = 'a1' WHERE id = 1;
1 ROW updated.
Elapsed: 00:00:00.07

在实例2上连接会话2:

SQL> SELECT name FROM v$database;
NAME
---------
ORCL
SQL> SELECT instance_number, instance_name FROM v$instance;
INSTANCE_NUMBER INSTANCE_NAME
--------------- ----------------
              2 orcl2
SQL> SET sqlp 'I2S2> '
I2S2> SHOW parameter _lm
NAME                                 TYPE                                 VALUE
------------------------------------ ----------- ------------------------------
_lm_dd_interval                      INTEGER                                 30
I2S2> SET timing ON
I2S2> UPDATE t_deadlock SET name = 'b2' WHERE id = 2;
1 ROW updated.
Elapsed: 00:00:00.04
I2S2> UPDATE t_deadlock SET name = 'a2' WHERE id = 1;

会话1上锁定记录2,产生死锁:

I1S1> UPDATE t_deadlock SET name = 'b1' WHERE id = 2;

第一次是实例2上的会话2被牺牲报错:

UPDATE t_deadlock SET name = 'a2' WHERE id = 1
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting FOR resource
 
Elapsed: 00:00:32.15
I2S2> UPDATE t_deadlock SET name = 'a2' WHERE id = 1;

可以看到,会话2等待30秒后报错,此时会话2执行同样的语句再次引发死锁:

UPDATE t_deadlock SET name = 'b1' WHERE id = 2
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting FOR resource
 
Elapsed: 00:01:00.39
I1S1> UPDATE t_deadlock SET name = 'b1' WHERE id = 2;

这次变成实例1上的会话1被牺牲报错,可以看到,会话1经历了两次死锁检测,因此执行时间为1分钟。会话1再次引入死锁:

UPDATE t_deadlock SET name = 'a2' WHERE id = 1
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting FOR resource
 
Elapsed: 00:01:01.69
I2S2>

被牺牲的又变成了会话2。
上面这个测试是在两个实例的_lm_dd_interval参数设置相同的情况下,下面修改实例2上的参数设置为5秒:

I2S2> ALTER system SET "_lm_dd_interval" = 5 scope = spfile sid = 'orcl2';
System altered.
Elapsed: 00:00:00.09
I2S2> shutdown immediate
DATABASE closed.
DATABASE dismounted.
ORACLE instance shut down.
I2S2> startup
ORACLE instance started.
Total System Global Area 281018368 bytes
Fixed SIZE 2095672 bytes
Variable SIZE 121636296 bytes
DATABASE Buffers 150994944 bytes
Redo Buffers 6291456 bytes
DATABASE mounted.
DATABASE opened.
I2S2> SHOW parameter _lm
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
_lm_dd_interval                      INTEGER     5
I2S2> UPDATE t_deadlock SET name = 'b2' WHERE id = 2;
1 ROW updated.
Elapsed: 00:00:00.06

实例2参数生效后连接会话更新该表,实例1上的会话1取消之前的修改,重新进行更新:

1 ROW updated.
Elapsed: 00:10:08.98
I1S1> ROLLBACK;
ROLLBACK complete.
Elapsed: 00:00:00.00
I1S1> UPDATE t_deadlock SET name = 'a1' WHERE id = 1;
1 ROW updated.
Elapsed: 00:00:00.01
I1S1> UPDATE t_deadlock SET name = 'b1' WHERE id = 2;

下面在实例2上的会话2,引入死锁:

I2S2> UPDATE t_deadlock SET name = 'a2' WHERE id = 1;
UPDATE t_deadlock SET name = 'a2' WHERE id = 1
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting FOR resource
 
Elapsed: 00:00:06.07
I2S2> UPDATE t_deadlock SET name = 'a2' WHERE id = 1;
UPDATE t_deadlock SET name = 'a2' WHERE id = 1
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting FOR resource
 
Elapsed: 00:00:05.95
I2S2> UPDATE t_deadlock SET name = 'a2' WHERE id = 1;
UPDATE t_deadlock SET name = 'a2' WHERE id = 1
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting FOR resource
 
Elapsed: 00:00:06.63
I2S2> UPDATE t_deadlock SET name = 'a2' WHERE id = 1;
UPDATE t_deadlock SET name = 'a2' WHERE id = 1
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting FOR resource
 
Elapsed: 00:00:05.89

显然由于不同实例的_lm_dd_interval参数的值设置不同,现在每次死锁都会在设置值更小的实例2上被检测,实例2上的会话每次都会被死锁牺牲掉。尝试设置不同的参数值在不同实例上设置死锁检测优先级获得成功。

Posted in ORACLE | Tagged , , , | Leave a comment

RAC全局死锁检测时间

对于单实例数据库而言,死锁的检测在秒级完成,而RAC环境则死锁的检测时间默认达到了1分钟。
对于单实例环境如果出现了死锁,那么马上其中一个进程就被中止,用户可以快速的得到错误返回。而对于RAC而言,死锁的检测并不是实时完成,而是需要60秒左右的时间。
会话1执行:

SQL> CREATE TABLE t_deadlock (id NUMBER PRIMARY KEY, name varchar2(30));
TABLE created.
Elapsed: 00:00:00.12
SQL> INSERT INTO t_deadlock VALUES (1, 'a');
1 ROW created.
Elapsed: 00:00:00.00
SQL> INSERT INTO t_deadlock VALUES (2, 'b');
1 ROW created.
Elapsed: 00:00:00.00
SQL> commit;
Commit complete.
Elapsed: 00:00:00.00
SQL> UPDATE t_deadlock SET name = 'a1' WHERE id = 1;
1 ROW updated.
Elapsed: 00:00:00.00

会话2执行:

SQL> SET timing ON
SQL> UPDATE t_deadlock SET name = 'b2' WHERE id = 2;
1 ROW updated.
Elapsed: 00:00:00.00
SQL> UPDATE t_deadlock SET name = 'a2' WHERE id = 1;

此时,会话2等待会话1的最终操作,下面会话1更新被会话2锁定的行,引发死锁:

SQL> UPDATE t_deadlock SET name = 'b1' WHERE id = 2;
UPDATE t_deadlock SET name = 'b1' WHERE id = 2
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting FOR resource
 
Elapsed: 00:01:00.12

可以看到,死锁的超时检测为1分钟。
而这个死锁的检测时间是可以调整的,Oracle通过隐含参数_lm_dd_interval控制:

SQL> conn / AS sysdba
Connected.
SQL> ALTER system SET "_lm_dd_interval" = 30 scope = spfile;
System altered.
SQL> shutdown immediate
DATABASE closed.
DATABASE dismounted.
ORACLE instance shut down.
SQL> startup
ORACLE instance started.
Total System Global Area 281018368 bytes
Fixed SIZE 2095672 bytes
Variable SIZE 104859080 bytes
DATABASE Buffers 167772160 bytes
Redo Buffers 6291456 bytes
DATABASE mounted.
DATABASE opened.

再次测试死锁的检测时间,会话1:

SQL> UPDATE t_deadlock SET name = 'a1' WHERE id = 1;
1 ROW updated.
SQL> SET timing ON

会话2执行更新:

SQL> SET timing ON
SQL> UPDATE t_deadlock SET name = 'b2' WHERE id = 2;
1 ROW updated.
Elapsed: 00:00:00.02
SQL> UPDATE t_deadlock SET name = 'a2' WHERE id = 1;

会话1执行更新引发死锁:

SQL> UPDATE t_deadlock SET name = 'b1' WHERE id = 2;
大约30秒后,会话2报错ORA-60<pre lang='SQL'>UPDATE t_deadlock SET name = 'a2' WHERE id = 1
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting FOR resource
 
Elapsed: 00:00:30.27

在10.2.0.2版本上,Oracle存在一个bug,允许这个参数设置为0,在10.2.0.3以后,这个bug被修正,如果设置为0后,则数据库无法正常启动:

[oracle@node1 ~]$ sqlplus / AS sysdba
SQL*Plus: Release 10.2.0.5.0 - Production ON Mon Jun 4 07:54:09 2012
Copyright (c) 1982, 2010, Oracle. ALL Rights Reserved.
Connected TO an idle instance.
SQL> startup
ORA-00067: invalid VALUE 0 FOR parameter _lm_dd_interval; must be at least 1

最后修改隐含参数是Oracle不推荐的,而且修改这个参数势必会影响RAC的正常工作方式导致LDM进程的繁忙度增加,而且可能影响RAC环境的稳定性和可用性。
如果确实对于前台的死锁检查时间要求较高,建议在测试环境中详细测试后再部署到产品环境中。

Posted in ORACLE | Tagged , , , , | 1 Comment

Oracle Ace Director

2012年6月23日端午节,图灵的百岁诞辰。今天一早收到了Oracle的邮件,我的Oracle Ace Director的申请批准了。
如果你没有Alan Turing那样的天才,那么想要成功只要靠后天的努力。
07年1月被评为Oracle的Ace,至今有5年半了,这次被Oracle评为Ace Director也是对自己付出的一种认可。
一分耕耘一分收获,不是付出了就一定有回报,但是没有付出就一定没有回报。这些道理每个人都懂,但是能真正做到的并不多。很多人可能在坚持了一段时间后没有看到结果就放弃了,而能够坚持到底的人绝大部分都成功了。
不敢说自己已经成功了,但是希望自己能作为一个例子,给一些刚进入Oracle数据库这个行业的人指一条道路——每天比别人更努力一点就可以了。

Posted in NEWS | 1 Comment