去年年底做了不少系统的数据迁移,大部分系统由于平台和版本的原因,做的是逻辑迁移,少部分做的是物理迁移,有一些心得体会,与大家分享。

首先说说迁移流程,在迁移之前,写好方案,特别是实施的方案步骤一定要写清楚,然后进行完整的测试。我们在迁移时,有的系统测试了四五次,通过测试来完善方案和流程。

针对物理迁移,也即通过RMAN备份来进行还原并应用归档的方式(这里不讨论通过dd方式进行的冷迁移),虽然注意的是要将数据库设为force logging的方式,在用RMAN做全备之前,一定要执行:

alter database force logging;

否则可能会产生坏块。

对于逻辑迁移,在job_processes设置为>0的数值之前,注意job的下次执行时间和job所属用户。比如job的定义在之前已经导入,但是在迁移之时,job已经运行过,那么迁移完成之后,job的下次时间还是原来的时间,这样可能会重复运行。另外,job通过IMP导入后,job所属用户会变成导入用户的名称,显然job原来的用户就不能对JOB进行管理了,可以通过下面的sql进行修改:

update sys.job$ set lowner=cowner , powner=cowner;

在迁移之前,应该禁止对系统进行结构上的修改和发布,比如表结构,索引,存储过程包等。

如果是用exp/imp导入的对象,包括存储过程等,应该检查对象是否与原生产库一致,比如由于dblink的原因,imp之后,存储过程不能创建,导致有部分存储过程丢失,尽管这些存储过程可能没有被使用。

下面是一些加快迁移速度的技巧:

  • 通过dblink,使用append insert的方式,同时利用并行,这种方式比exp/imp更快
  • 对于有LONG类型的列,insert..select的方式显然是不行的,可以通过exp/imp的方式,但是这种方式速度非常慢,其原因在于imp时一行一行地插入表。有另外一种方式,即sqlplus的copy命令,下面是一个示例:
    spool copy_long_table_1.log
    conn / as sysdba
    set copycommit=2000
    set arraysize 30
    set long 10485760
    
    copy from system/xxxx@source_db append username.table_name using select * from username.table_name;
    
    spool off
    exit
    

    不过,sqlpus的copy命令不支持有timestamp和lob列类型的表。如果有timestamp类型的表,可以通过在exp时,加上rowid的条件,将一个表分成多个部分同时操作,对于有lob类型的表,也可以同样处理(因为insert ...select方式下,有lob类型列时,也同样是一行一行地插入)。注意在这种方式下,就不能使用direct的方式exp/imp。下面是exp导出时parfile示例:

    query="where rowid>=dbms_rowid.rowid_create(1,71224,52,9,0) and rowid<=dbms_rowid.rowid_create(1,71224,55,1038344,10000)" 
    file=/dumpdata/n1.dmp
    tables=username.table1
    constraints=n
    grants=no
    indexes=no
    buffer=104857600
    ...
    ...
    query="where rowid>=dbms_rowid.rowid_create(1,71224,423,137,0) and rowid<=dbms_rowid.rowid_create(1,71224,432,59272,10000)" 
    file=/dumpdata/n6.dmp
    tables=username.table1
    constraints=n
    grants=no
    indexes=no
    buffer=104857600
    

    将表分成几部分同时操作,不仅仅可以利用rowid,也可以利用表上的列,比如说,表上有一个created_date的列,并且保证是递增插入数据,那么这种情况下,也可以使用这个字段将表分成不同的范围同时进行导出和导入。不过使用ROWID通常具有更高的效率。
    当然对于有lob列的表,可以按上述方式,拆成多个insert方式同时插入,不需要exp/imp。

  • 对于特别大的分区表,虽然使用并行可以提高速度,但是受限于单个进程(不能跨DB LINK进行并行事务,只能并行查询,也即insert..select只能是SELECT部分才能进行并行)的处理能力,这种方式下速度仍然有限。可以并行将数据插入多个中间表,然后通过exchange partition without validation 的方式,交换分区,这种方式将会大大提高了速度。
    有朋友可能会问,为什么不并行直接插入分区表,当然如果是非direct path(append)方式,则是没问题的,但是这种方式插入的性能较低。而direct path的方式,会在表上持有mode=6(互斥)的TM锁,不能多个会话同时插入。(update: 在insert 时使用这样的语句:insert into tablename partition (partname) select * from tablename where ....,更简单更有效率。)
  • 迁移时,将数据分成两部分,一部分是历史表,第二部分是动态变化的表,在迁移之前,先导入历史表,并在历史表上建好索引,这无疑会大大减少迁移时业务系统中断时间。
  • 迁移之前,考虑清理掉垃圾数据。
  • 迁移时,应保证表上没有任何索引,约束(NOT NULL除外)和触发器,数据导入完成后,再建索引。建索引时同样,同时使用多个进程跑脚本。索引创建无成后,应去掉索引的PARALLEL属性
  • 在创建约束时,应按先创建CHECK约束,主键,唯一键,再创建外键约束的顺序。约束状态为 ENABLE NOVALIDATE,这将大大减少约束创建时间。而在迁移完成后,再考虑设回为ENABLE VALIDATE。
  • 通过使用dbms_stats.export_schame_stats和dbms_stats.import_schame_stats导入原库上的统计信息,而不用重新收集统计使用。

朋友们可以看到,以上均是针对9i的,实际上在10g甚至11g环境下,也仍然很多借鉴意义。当然这些技巧不仅仅用于完整的数据库迁移,也可以应用到将个别表复制到其他数据库上。

这里没有提到的是利用物化视图或高级复制、触发器之类的技术,因为这些技术,毕竟要修改生产库,对生产库的运行有比较大的影响,因此,只有在停机时间要求特别严格,而在这个时间内又不能完成迁移时才应该考虑。

从迁移的经验来说,只有完善的流程,完整的测试才可以保证成功。这里只是列举了一些小技巧,如果对整个迁移过程有兴趣,可以针对这个话题再进行讨论。

在一个表上建索引时,报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错误。

在以前的一篇文章《DBMS_STATS、ANALYZE以及Global Statistics》中,提到使用10g数据库dbms_stats收集统计信息时,granularity缺省值为“AUTO”,其含义是“Auto -- Table + Partition + Subpartition (10g,表+分区,当子分区是list分区时还包括子分区)”。本文就这个问题再深入地探讨一下。

大家都知道,子分区有两种,一种是分区为RANGE,子分区为HASH,另一种是分区为RANGE,子分区为LIST。在10g数据库中,如果在使用dbms_stats收集统计信息时,如果没有显式指定granularity(粒度),那么granularity就会取自dbms_stats配置:
而其缺省值是“AUTO",而不再是9i下的”DEFAULT":

SQL> select dbms_stats.get_param('granularity') param from dual;

PARAM
------------------------------
AUTO

而10g自带的自动收集统计信息的任务“GATHER_STATS_JOB",其granularity同样是取自granularity param。当然可以通过下面的SQL来更改其值:

SQL> exec dbms_stats.set_param('granularity','global and partition');

这样更改后,dbms_stats默认就会收集表以及分区级统计信息,不收集子分区级统计信息。

那么,granularity=auto时,到底是怎么样的呢?前面说到了子分区是以list方式分区时,那么就会收集子分区级统计信息,其言外之意就是如果子分区是以hash方式分区时就不会收集子分区统计信息了。到底是不是这样呢?下面做个测试,测试环境是Oracle 10.2.0.4 for Linux AS4:

QL> create table t1
  2  partition by range(object_id)
  3  subpartition by hash(data_object_id)
  4  subpartitions 4
  5  ( partition p1 values less than(10000),
  6    partition p2 values less than(20000),
  7    partition p3 values less than (maxvalue)
  8  )
  9  as select * from dba_objects;  

Table created.

SQL> create table t2
  2  partition by range(object_id)
  3  subpartition by list(object_type)
  4  subpartition template(
  5    subpartition sp1 values ('TABLE'),
  6    subpartition sp2 values ('INDEX'),
  7    subpartition sp3 values ('VIEW'),  
  8    subpartition sp4 values (DEFAULT)
  9  )  
 10  ( partition p1 values less than(10000),
 11    partition p2 values less than(20000),
 12    partition p3 values less than (maxvalue)
 13  )
 14  as select * from dba_objects; 

Table created.

我们先建再从个测试表,表T1是RANGE+HASH方式的复合(组合)分区表,表T2是RANGE+LIST方式的复合分区表。
下面将"granularity" param重新设回为”auto“,然后收集T1和T2的统计信息:

Read the rest of this entry

说起来汗颜,我这个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这两个进程,检查共享内存段,发现这个异常的共享内存段自动被清除。再检查内存的使用,内存的使用率也大幅下降,回到正常状态。

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

我们都知道drop table, truncate table时都会先做一次checkpoint,将被删除对象的脏块写入磁盘。

客户有一套系统,Oracle 9.2.0.8,需要做数据迁移,由于种种原因,采用的是逻辑迁移的方式。由于库比较大,超过了1.5T,而停机时间又有限,因此在正式迁移之前需要做大量的测试,测试的目的,一方面是看迁移流程上是否存在问题,另一方面是看迁移的时候,哪个地方会存在性能瓶颈,并进行优化,同时估算实施迁移时间。

第一次测试后,需要把测试产生的大量用户及其对象全部删除,删除用的是drop user username cascade。不幸的是这种方式删除得相当地慢。一个9000多个表的用户,删除了1个半小时才删除了4000多个表。为什么这么慢?有没有办法提高速度?

drop table既然要做checkpoint,那么在db cache非常大的情况下,这需要消耗的时间是比较长的。如果能够减少这个时间无疑将大幅提高速度。首先尝试做一次checkpoint,将buffer cache全部刷新出去:

alter system checkpoint;
alter session set events 'immediate trace name flush_cache level 1';

发现没什么效果。

由于db_cache_size有50GB左右,db_keep_cache_size有6G左右。重新设置参数,将db_keep_cache_size设为0,将db_cache_size设为200M,重启一下数据库,重新执行删除用户的操作,操作很快完成。

在另一次同样的过程中,采用同样的修改参数的方式,效果同样非常明显。

这是个简单的案例,与君共享。

前几天客户遇上这样一个问题,某个用户A将视图的SELECT给予另一个用户B,但是用户B查询这个视图时,仍然报错:ORA-01031: 权限不足。这是怎么一回事呢?下面来模拟一下这个过程:

有三个用户test1,test2,test3, 三个用户都具有DBA色色权限。

用TEST1用户创建一个表T1,并将其查询权限授予TEST2:

SQL> create table t1 as select * from all_objects;

表已创建。

SQL> grant select on t1 to test2;

授权成功。

用TEST2用户创建一个视图,视图的基表是TEST1.T1,并将查询权限授予TEST3:

SQL> create view v_t1 as select * from test1.t1;

视图已建立。

SQL> grant select on v_t1 to test3;

授权成功。

TEST3用户查询视图TEST2.V_T1:

SQL> select * from test2.v_t1 where rownum<1;
select * from test2.v_t1 where rownum<1
                    *
ERROR 位于第 1 行:
ORA-01031: 权限不足

可以看到报了权限不足的错误,就算这里TEST3用户有DBA权限。
这到底是怎么回事呢?
其实视图的权限,有两点需要引起注意:

1. 视图中,类似于定义者权限的存储过程,是屏蔽了角色权限的。比如如果TEST1没有显式地将T1表的SELECT权限给予TEST2,那么TEST2在创建视图V_T1时也会报ORA-01031错误,即使TEST2用户拥有DBA角色权限。

2.如果在用户A的视图中,引用了其他用户B的表,用户A将视图的访问权限给予用户C,那么就变相地将用户B的表的访问权限给予了用户C,因此,用户A必须有将用户B的表的访问权限转授用户C的权限,也就是用户B在授予A权限时,必须使用with grant option。

显然这里正是由于第2点的原因,导致用户TEST3不能访问视图。用户TEST1执行下面的操作,将解决这个问题:

SQL> grant select on t1 to test2 with grant option;

授权成功。

对于视图的UPDATE,DELETE权限,同样是如此。

在测试时,有一个现象,有点意思。就是如果用户TEST2没有显式地把V_T1的SELECT权限授予TEST3,而TEST3在有SELECT ANY TABLE或DBA权限时,则查询这个视图时不会报权限不足的错误。由于有SELECT ANY TABLE权限的存在,所有的用户表都可以被访问。但是显式授予表的权限时,似乎表的权限有更高的优先级,并且没有跟系统权限和角色权限进行结合。或者版本不同,表现得不一样,在我的测试中,是Oracle 9.2.0.8 for Windows。

,

在自动UNDO管理模式下,我们有时仍然想手动删除UNDO段。比如某个UNDO段出现了逻辑坏块。
下面首先来看看,直接删除UNDO段能不能成功。

SQL> drop rollback segment "_SYSSMU9$";
drop rollback segment "_SYSSMU9$"
*
ERROR 位于第 1 行:
ORA-30025: 不允许 DROP 段 '_SYSSMU9$' (在撤消表空间中)

看来是行不通的。那么怎么样才能删除呢?试试下面的办法:

SQL> alter session set "_smu_debug_mode"=4;

会话已更改。

SQL> drop rollback segment "_SYSSMU9$";
drop rollback segment "_SYSSMU9$"
*
ERROR 位于第 1 行:
ORA-01545: 指定的回退段'_SYSSMU9$'不可用

还是不行。下面我们看看UNDO段的状态:

SQL> select segment_name,status from dba_rollback_segs;

SEGMENT_NAME                   STATUS
------------------------------ ----------
SYSTEM                         ONLINE
_SYSSMU1$                      ONLINE
_SYSSMU2$                      ONLINE
_SYSSMU3$                      ONLINE
_SYSSMU4$                      ONLINE
_SYSSMU5$                      ONLINE
_SYSSMU6$                      ONLINE
_SYSSMU7$                      ONLINE
_SYSSMU8$                      ONLINE
_SYSSMU9$                      ONLINE
_SYSSMU11$                     OFFLINE

发现这个要删除的UNDO状态为ONLINE。下面我们将UNDO段置为OFFLINE状态,再删除:

SQL> alter rollback segment "_SYSSMU9$" offline;

回退段已变更。

SQL> drop rollback segment "_SYSSMU9$";

回退段已删除。

可以看到UNDO段已经被删除。这里首先把UNDO段OFFLINE,然后再DROP。值得注意的是,在没有修改"_smu_debug_mode"的情况下,UNDO段是不能OFFLINE的。

总结:
要在UNDO自动管理模式下删除UNDO段,需要三个步骤:

  • 执行alter session set "_smu_debug_mode"=4;
  • 执行 alter rollback segment "undo-segment-name" offline;
  • 执行 drop rollback segment "undo-segment-name" ;

一套运行在Linux下的Oracle 9.2.0.4的库,出现了大量的ORA-600[4042]错误。

ORA-00600: internal error code, arguments: [4042], [31760], [], [], [], [], [], []

关于ORA-600错误,第一个参数,也就是第一个方括号中的标识,通常可以用来定位Oracle错误发生的内部模块。(可以参考Metalink Doc ID 146580.1:What is an ORA-600 Internal Error?)如果是数字,最高位通常是指一个大的模块,而接下来的一位是小的模块。比如这里[4042],4000,最高位是4,是Transaction Layer(事务层),而次高位是0,Transaction Undo(详见Metalink Doc ID : 175982.1 ORA-600 Lookup Error Categories)。

针对这个错误,很明显是跟事务有关。在处理的时候,第一反应肯定是检查TRACE文件:

ORA-00600: internal error code, arguments: [4042], [31760], [], [], [], [], [], []
Current SQL statement for this session:
INSERT INTO XXX .....
....
----- Call Stack Trace -----
calling              call     entry                argument values in hex      
location             type     point                (? means dubious value)     
-------------------- -------- -------------------- ----------------------------
ksedmp()+269         call     ksedst()+0           0 ? 0 ? 0 ? 0 ? BFFF90A4 ?
                                                   A16D886 ?
ksfdmp()+14          call     ksedmp()+0           3 ? BFFF91B0 ? 98585B4 ?
                                                   AD58FA0 ? 3 ? A4B929C ?
kgeriv()+188         call     ksfdmp()+0           AD58FA0 ? 3 ?
kgeasi()+108         call     kgeriv()+0           AD58FA0 ? AD9AFC0 ? FCA ? 1 ?
                                                   BFFF91EC ?
ktugusc()+787        call     kgeasi()+0           AD58FA0 ? AD9AFC0 ? FCA ? 2 ?
                                                   1 ? 4 ? 7C10 ?
ktuswr()+2049        call     ktugusc()+0          BFFF9394 ? D ? 1 ? 0 ? 0 ?
                                                   0 ? 0 ? 0 ?
ktusmous_online_und  call     ktuswr()+0           D ? 0 ? 0 ? 0 ? 0 ? 1 ?
oseg()+898                                         
ktusmaus_add_us()+3  call     ktusmous_online_und  1 ? 1 ? BFFF94F8 ? 1 ?
27                            oseg()+0             
ktubnd()+7646        call     ktusmaus_add_us()+0  BFFF9CEC ? 0 ?
ktuchg()+581         call     ktubnd()+0           BFFF9678 ? 8468F4F0 ?
                                                   BFFF9CEC ? 0 ?
ktbchg2()+318        call     ktuchg()+0           2 ? 89E91A08 ? 1 ? B7BC3484 ?
                                                   B7BC348C ? AD7BECC ?
                                                   BFFF9CEC ? AD7BE38 ? 0 ? 0 ?
kdtchg()+1406        call     ktbchg2()+0          0 ? 89E91A08 ? B7BC3484 ?
                                                   B7BC348C ? AD7BECC ?
                                                   BFFF9CE4 ? AD7BE38 ? 0 ? 0 ?
kdtwrp()+2272        call     kdtchg()+0           B7BA8638 ? B7BC3484 ?
                                                   B7BC348C ? AD7BECC ?
                                                   AD7BE38 ? 1 ? 1C6 ?
kdtInsRow()+1724     call     kdtwrp()+0           B7BA8638 ? B7BA0000 ?
                                                   60DD40A4 ? B7BC31AC ? C ?
                                                   2C88E28 ?
insrow()+275         call     kdtInsRow()+0        B7BA8638 ? 89E95354 ?
                                                   89E950EC ? B7BA8418 ?
                                                   9840000 ? AD589E8 ?
insdrv()+2566        call     insrow()+0           B7BA8638 ? BFFF9FEC ? 0 ?
insexe()+1665        call     insdrv()+0           B7BA8638 ? 89E950EC ? 0 ?
                                                   B7BA8418 ? 0 ? 0 ?
opiexe()+10831       call     insexe()+0           89E95354 ? BFFFA220 ?
opipls()+6068        call     opiexe()+0           4 ? 3 ? BFFFA98C ?
opiodr()+5238        call     kjxsupd()+987        66 ? 6 ? BFFFB64C ?
rpidrus()+140        call     opiodr()+0           66 ? 6 ? BFFFB64C ? 5 ?
skgmstack()+211      call     rpidrus()+0          BFFFB028 ? 10 ? BFFFB040 ?
                                                   BFFFB3E4 ? BFFFB028 ?
                                                   899782A ?
rpidru()+93          call     skgmstack()+0        BFFFB040 ? AD5A760 ? F618 ?
                                                   899782A ? BFFFB028 ?
rpiswu2()+777        call     rpidru()+0           BFFFB3E4 ? 40 ? 40 ? 0 ?
                                                   40C ? A4B929C ?
rpidrv()+1452        call     rpiswu2()+0          837327D8 ? 40 ? BFFFB4E8 ?
                                                   2 ? BFFFB508 ? 40 ?
psddr0()+113         call     rpidrv()+0           5 ? 66 ? BFFFB64C ? 3A ?
                                                   AD5907C ? BFFFB7F8 ?
psdnal()+173         call     psddr0()+0           5 ? 66 ? BFFFB64C ? 30 ? 20 ?
                                                   B7BBB6B8 ?
pevm_EXECC()+458     call     psdnal()+0           BFFFC844 ? BFFFC834 ?
                                                   AD53500 ? B7BBB6B8 ?
                                                   856EA21C ? 856EA21C ?
pfrrun()+31877       call     pevm_EXECC()+0       B7BBF19C ? AD9E4C0 ? 20 ?
peicnt()+291         call     pfrrun()+0           B7BBF19C ? 0 ? AD9E31C ?
                                                   ADA0464 ? AD5907C ?
                                                   BFFFCC10 ?
kkxexe()+451         call     peicnt()+0           BFFFC844 ? B7BBF19C ? 2 ?
                                                   AD9954C ? 5001AA24 ? 0 ?
opiexe()+12624       call     kkxexe()+0           B7BBD068 ? B7BB022C ?
                                                   AD53504 ? B7BBD068 ? 0 ? 0 ?
opiall0()+4435       call     opiexe()+0           4 ? 3 ? BFFFD064 ?
opial7()+441         call     opiall0()+0          3E ? 22 ? BFFFD164 ?
                                                   BFFFDC0C ? BFFFD1EC ? 0 ?
opiodr()+5238        call     kjxsupd()+987        47 ? F ? BFFFDC0C ?
ttcpip()+2124        call     opiodr()+0           47 ? F ? BFFFDC0C ? 1 ?
Cannot find symbol in /lib/tls/libc.so.6.
opitsk()+1635        call     ttcpip()+0           AD53500 ? 47 ? BFFFDC0C ? 0 ?
                                                   BFFFE4E4 ? BFFFE4E0 ?
opiino()+602         call     opitsk()+0           0 ? 0 ? AD53500 ? AD8D7B8 ?
                                                   83 ? 0 ?
opiodr()+5238        call     kjxsupd()+987        3C ? 4 ? BFFFF8B0 ?
opidrv()+517         call     opiodr()+0           3C ? 4 ? BFFFF8B0 ? 0 ?
sou2o()+25           call     opidrv()+0           3C ? 4 ? BFFFF8B0 ?
main()+182           call     sou2o()+0            BFFFF894 ? 3C ? 4 ?
                                                   BFFFF8B0 ? 1 ? 0 ?
00622DE3             call     main()+0             2 ? BFFFF954 ? BFFFF960 ?
                                                   5FBC66 ? 734FF4 ? 0 ?

从SQL来看,是个简单的INSERT语句,那么就涉及到事务处理了。
从call stack来看,在stack顶端,下面的几行表明错误应该是跟回滚段有关。

# ktugusc()+787        call     kgeasi()+0           AD58FA0 ? AD9AFC0 ? FCA ? 2 ?  
#                                                    1 ? 4 ? 7C10 ?  
# ktuswr()+2049        call     ktugusc()+0          BFFF9394 ? D ? 1 ? 0 ? 0 ?  
#                                                    0 ? 0 ? 0 ?  
# ktusmous_online_und  call     ktuswr()+0           D ? 0 ? 0 ? 0 ? 0 ? 1 ?  
# oseg()+898                                           
# ktusmaus_add_us()+3  call     ktusmous_online_und  1 ? 1 ? BFFF94F8 ? 1 ?  
# 27                            oseg()+0                        

这一步的分析其实很快,基本上凭call stack中的函数名字来判断。由于当时有其他的事情在处理,同时也不在这套库的现场,就让DBA重新创建了一个UNDO表空间,并将UNDO_TABLESPACE参数设置为新的UNDO表空间名字,错误就不在出现。

其实这个故障的处理,类似于去年处理的一个案例,详见记一次并行恢复问题导致Oracle数据库Crash故障的处理

一个简单的故障处理过程,记录下来,供朋友们参考。

本文简单记录一下最近一次数据恢复的过程。

事情的起因是,一个应用升级后,某一个操作导致一个表的几个列全部被更新为同一值(忍不住又要唠叨测试的重要性)。这样的错误居然出现在应用代码中,显然是重大的BUG。那个是罪魁祸首的SQL,UPDATE语句,其WHERE条件仅仅只有一个where 1=1。

系统的维护人员称是星期五出的错,发现出错是在星期天,也就是我恢复数据的日期,与声称的出错时间已经隔了将近2天。开始尝试用flashback query恢复数据,报ORA-01555错误,此路不通。维护人员说,星期五之前的RMAN备份已经被删除了(又是一个备份恢复策略不当地例子),使用基于时间点的恢复也不可能了。剩下的一条路,只有使用log miner。还好归档文件还在数据库服务器上。

这套库是一套RAC数据库,由于没有人能确认操作发生在哪个节点,因此需要将一个节点下所有的归档复制到另一个节点上(如果没有足够的空间,可以使用NFS)。然后需要找到我们用于数据恢复的归档日志:

set linesize 170 pagesize 10000
alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss';

col name for a30
col first_change for a10
col next_change for a10

select max(first_time) from v$archived_log 
where first_time < to_date('200909251900','yyyymmddhh24mi'); --这里的时间为错误发生时估计的最早时间。

select sequence#,first_time,name,to_char(first_change#,'xxxxxxxx') first_change,
 to_char(next_change#,'xxxxxxxx') next_change
 from v$archived_log 
where  first_time >=to_date('200909251707','yyyymmddhh24mi')  
order by 2;--这里的时间为前一SQL的max(first_time)结果

 SEQUENCE# FIRST_TIME          NAME                           FIRST_CHAN NEXT_CHANG
---------- ------------------- ------------------------------ ---------- ----------
      4039 2009-09-25 17:07:10 /arch/db1_1_4039.arc          88ce7eff   88d1457c
      4040 2009-09-26 12:24:52 /arch/db1_1_4040.arc          88d1457c   88d1459f
      4041 2009-09-26 12:25:22 /arch/db1_1_4041.arc          88d1459f   88d156a4
      4688 2009-09-26 12:37:59 /arch/db1_2_4688.arc          88d1457f   88d1464a
      4689 2009-09-26 12:38:27 /arch/db1_2_4689.arc          88d1464a   88d1569c
      4042 2009-09-26 12:54:44 /arch/db1_1_4042.arc          88d156a4   88d157e7
      4043 2009-09-26 12:54:56 /arch/db1_1_4043.arc          88d157e7   88d1ab06
      4690 2009-09-26 13:07:47 /arch/db1_2_4690.arc          88d1569c   88d1570b
      4691 2009-09-26 13:08:00 /arch/db1_2_4691.arc          88d1570b   88d1ab09
      4044 2009-09-26 15:27:32 /arch/db1_1_4044.arc          88d1ab06   88d1ab0d
      4045 2009-09-26 15:27:35 /arch/db1_1_4045.arc          88d1ab0d   88d25091
      4692 2009-09-26 15:40:36 /arch/db1_2_4692.arc          88d1ab09   88d1ab77
      4693 2009-09-26 15:40:39 /arch/db1_2_4693.arc          88d1ab77   88d25094
      4046 2009-09-26 22:24:07 /arch/db1_1_4046.arc          88d25091   88d250db
      4047 2009-09-26 22:24:19 /arch/db1_1_4047.arc          88d250db   88d2515e
      4048 2009-09-26 22:24:29 /arch/db1_1_4048.arc          88d2515e   88d25167
      4049 2009-09-26 22:24:41 /arch/db1_1_4049.arc          88d25167   88d25cac
      4694 2009-09-26 22:37:13 /arch/db1_2_4694.arc          88d25094   88d25147
      4695 2009-09-26 22:37:25 /arch/db1_2_4695.arc          88d25147   88d2515b
      4696 2009-09-26 22:37:33 /arch/db1_2_4696.arc          88d2515b   88d2516a
      4697 2009-09-26 22:37:47 /arch/db1_2_4697.arc          88d2516a   88d25ca9
      4050 2009-09-26 22:41:57 /arch/db1_1_4050.arc          88d25cac   88d25cde
      4698 2009-09-26 22:55:01 /arch/db1_2_4698.arc          88d25ca9   88d25dcf
      4699 2009-09-26 22:55:19 /arch/db1_2_4699.arc          88d25dcf   88dbd27e

尝试找到数据被错误更新的时间点:

exec sys.dbms_logmnr.add_logfile(logfilename=>'/arch/db1_1_4038.arc');
exec sys.dbms_logmnr.add_logfile(logfilename=>'/arch/db1_1_4039.arc');

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

col sql_redo for a50

select scn,timestamp,username,sql_redo from v$logmnr_contents 
where operation='UPDATE' and upper(sql_redo) like '%TBL_FORM_FORM%' 
and sql_redo like '%SGS0900021BNc10%'  --这个值是UPDATE时某一列被更新后的值,用在这里便于查找。
order by scn,timestamp;
exec sys.dbms_logmnr.end_logmnr;

很不幸的是,没有找着需要的数据。再往后找了几个日志,也没找着。
如果一直找下去,显然会消耗比较长的时间,业务也已经停止了。不过可以用一种简单的方法来查找数据被错误更新发生的时间:一个比较大的表,通常段头后面的那个块,也就是存储那个表的数据的第1个块,通常是很少更新的,至少当时恢复的那个表是这样一种情况。我们可以通过数据块中ITL上的事务SCN来满足我们的要求。

SQL> select tablespace_name,extent_id,file_id,block_id,blocks
     from dba_extents where owner='XXX'
     and segment_name='TBL_FORM_FORM'
     order by extent_id;

TABLESPACE_NAME   EXTENT_ID    FILE_ID   BLOCK_ID  BLOCKS
---------------- ---------- ---------- ---------- -------
XXXX                      0         16      25481     128
XXXX                      1         17      23433     128
XXXX                      2         18      21385     128
XXXX                      3         19      19977     128
XXXX                      4         16      23945     128
XXXX                      5         17       8585     128
XXXX                      6         18      14217     128
XXXX                      7         19      18825     128

SQL> alter system dump datafile 16 block 25482;

System altered.

Start dump data blocks tsn: 4 file#: 16 minblk 25482 maxblk 25482
buffer tsn: 4 rdba: 0x0400638a (16/25482)
scn: 0x0000.88e21027 seq: 0x02 flg: 0x00 tail: 0x10270602
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
Block header dump:  0x0400638a
 Object id on Block? Y
 seg/obj: 0x40d8  csc: 0x00.88e20c40  itc: 2  flg: -  typ: 1 - DATA
     fsl: 0  fnx: 0x0 ver: 0x01

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0010.011.0006ed74  0x03c002a0.2f48.07  C---    0  scn 0x0000.88d7af30
0x02   0x0012.019.000027e0  0x03c00ede.05de.42  C---    0  scn 0x0000.44e2ee39

从上面的结果可以看到,数据块的ITL中,最新的事务其SCN为88d7af30,正处于最后一个归档日志的first_change#和last_change#之间,即88d25dcf和88dbd27e之间,难不成这个错误是今天早上才发生的?于是我挖掘最后1个归档日志,结果发生错误的确是发生在早上,也就是我开始进行恢复操作之前半个小时。

既然错误并没有发生太久,同时这个系统也允许一定的数据丢失,那就使用flashback query,得到UPDATE操作之前的数据即可。

create table tbl_form_form_new 
as select * from tbl_form_form
as of timestamp to_date('2009-09-27 09:08:00','yyyy-mm-dd hh24:mi:ss');
--当然这里也可以按SCN进行闪回。

幸运的是,这次闪回查询成功了。看起来足够大的UNDO表空间还是有好处,至少我已经有数次用闪回查询来恢复数据。

,

其实这篇文章,说讲述的跟DBA没有太大的关系,但是我写出来,希望对DBA有所帮助。

上个月一客户的某重要业务,出现2小时故障,事情闹得很大。故障发生在一个简单的INSERT语句上面,不幸的时执行那个SQL时总是报ORA-600[kcbget_24]这样的错误,其实导致事务一直失败。不过幸运的是,那个INSERT语句所要完成的业务功能只是整个业务环节中的可选功能,开发商也有开关来控制是否启动这个可选功能。虽然这个ORA-600错误,不能很快查到原因,但是却可以通过设置那个业务功能的开关来绕过这个问题。当时是我在现场处理的这个问题,从接到问题报告到解决问题,只花了很短的时间。那为什么整个故障历时了2小时。其主要原因在于,这个客户没有有效的监控手段,来监控错误,完全靠业务人员的反映,而业务人员又不能很快地发现问题。

事后检查错误出现的原因,发现是INSERT语句时,维护一个索引出现了索引,在做索引节点块分裂时报了ORA-600[kcbget_24]错误。ORA-600错误基本上是由BUG或数据损坏引起的,但BUG引起的可能性更大。那为什么之前好好的,怎么突然就出现错误了,再进一步追查发现出现问题的索引是新建的索引,而建这个索引的,是开发商的一个开发人员,未经过流程,就直接在表上建了。结果索引一建好,就出现问题了。

这里我想说的是,做DBA工作,不能有任何侥幸心理。这次发生的事故,是ORACLE的BUG引起,但是如果之前有过充分的测试,按流程来操作,很可能就分避免了这个问题的产生。对一个复杂的系统的维护,技术或许比较重要,但是我认为,流程和测试更重要。

这件事也让我想起2年前,我还在上一家单位(相对目前的工作性质来说是甲方)时,一个非常重要的业务系统,在一个非常重要的时间,崩溃了,完全不能用。这套系统本来还有一个月就功成身退,被新的业务系统所代替,结果最后一班岗也没站好,潜伏的一个致命的BUG爆发了(不是数据库软件的BUG,是业务软件的BUG)。头一天晚上系统的配置数据做了改动,按正常的流程是需要测试的,或许业务人员认为,配置的改动是常有的事,不需要测试,结果忽略了测试,结果第二天.....

工作中的某些环节,比如测试,是枯燥的,但是实在是不可或缺的。