一些数据迁移的技巧

Posted by 老熊 on 3月 1st, 2010

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

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

针对物理迁移,也即通过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环境下,也仍然很多借鉴意义。当然这些技巧不仅仅用于完整的数据库迁移,也可以应用到将个别表复制到其他数据库上。

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

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

统计信息与子分区

Posted by 老熊 on 12月 19th, 2009

在以前的一篇文章《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 »

视图与权限

Posted by 老熊 on 11月 14th, 2009

前几天客户遇上这样一个问题,某个用户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段

Posted by 老熊 on 10月 30th, 2009

在自动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” ;

记一次ORA-600[4042]故障的处理

Posted by 老熊 on 10月 17th, 2009

一套运行在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故障的处理

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

DBA工作随想

Posted by 老熊 on 9月 14th, 2009

其实这篇文章,说讲述的跟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)。头一天晚上系统的配置数据做了改动,按正常的流程是需要测试的,或许业务人员认为,配置的改动是常有的事,不需要测试,结果忽略了测试,结果第二天…..

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

仔细考虑维护脚本

Posted by 老熊 on 8月 5th, 2009

有下面一段SQL脚本,朋友们看能不能找出问题所在?

create table t1_bak as select * from t1 order by col_1,col_2;
truncate table t1;
insert /*+ append */ into t1 select * from t1_bak;

先不看这段SQL代码的效率如何,我们来关注一下这段代码存在的严重的安全问题。

这段代码从语法上看完全没有问题,然而….
假如这段代码是用sqlplus来运行,而第1条create table语句由于空间不足,或者由于数据量太大,临时表空间不够,排序出错,那么t1_bak的创建就会失败,而紧接着,t1会被truncate掉,结果可想而知。

这不是我临时想来的问题,而是真真实实发生在现实中的。现实中发生的这件事,比我提到的隐含的问题就明显,就是第1条create table语句,存在语法问题,结果,表被truncate了,数据丢失了。

仔细考虑维护脚本,甚为重要。

TAF PartIV

Posted by 老熊 on 5月 1st, 2009

与本系列的前几篇不同,本文将在RAC上测试TAF的一些特性。而测试环境又有所不同:运行于Red Hat Enterprise Linux 5上的Oracle 10.2.0.1,客户端为Windows上的10.2.0.1。在客户端的TNSNAME配置如下:

DMDB =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.81)(PORT = 1521))
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.82)(PORT = 1521))      
    )
    (CONNECT_DATA =
      (SERVICE_NAME = dmdb)
      (FAILOVER_MODE =
        (TYPE = SELECT)
        (METHOD = BASIC)
        (RETRIES = 180) (DELAY = 5)         
      )      
    )
  )

我们使用sqlplus连接到数据库中(为节省篇幅,对部分无关紧要的输出做了剪裁):

D:\>sqlplus test/test@dmdb
连接到:
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, Real Application Clusters, OLAP and Data Mining options

由于负载均衡的作用,我们需要确定会话连接的实例(节点),下面的输出给出了当前会话连接的节点和会话信息:

SQL> show parameter instance_name

NAME                                 TYPE        VALUE
------------------------------------ ----------- -------
instance_name                        string      rac2

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       147
SQL> select failover_type,failover_method,failed_over from v$session where sid=147;

FAILOVER_TYPE FAILOVER_M FAILED_OVE
------------- ---------- ----------
SELECT        BASIC      NO      

从上面输出的结果中的最后几行可以看出,会话已经启用了TAF

现在将节点2上的实例(也就是rac2)关闭,在sqlplus依次执行下面的命令,得到的结果如下:

SQL> show parameter instance_name

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------
instance_name                        string      rac1

SQL> select sid from v$mystat where rownum=1;

       SID
----------
       146
       
SQL> select failover_type,failover_method,failed_over from v$session where sid=146;

FAILOVER_TYPE FAILOVER_M FAILED_OVE
------------- ---------- ----------       
SELECT        BASIC      YES


从结果来看,会话已经顺利地failover到了第1个节点(rac1)上

在接下来的测试中,为了避免服务器端的自动均衡对测试造成的干扰,我们将两个实例的remote_listener参数设置为空,并使用lsnrctl services命令确认设置已经生效:

SQL> alter system set remote_listener=” sid=’rac1′;

System altered.

SQL> alter system set remote_listener=” sid=’rac2′;

System altered.

同时将客户端tnsname设置中原来的两个IP地址改为一个IP地址,即将:

(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.81)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.82)(PORT = 1521))

改为:

(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.81)(PORT = 1521))

退出sqlplus,再次运行sqlplus,进行跟上面同样的测试:
Read the rest of this entry »

Oracle与防火墙

Posted by 老熊 on 4月 20th, 2009

最近有两次Oracle数据库故障与防火墙有关。这里的防火墙是硬件网络防火墙,而不是软件防火墙。

先说说简单的。一个运行在Windows系统上的Oracle 9i,客户端不能连接数据库,但是用tnsping测试没有问题。解决问题的办法很简单,但是我们仍然需要了解一下引起这个问题的原因。

这个问题首先得从客户通通过监听连接数据库的整个过程说起,此处指专用服务器连接模式:

  • 服务器上的监听进程在1521端口上进行侦听
  • 客户端发起一个数据库连接请求
  • 监听进程fork一个Oracle服务器进程(Server Process),也可称之为影子进程 (Shadow Process)。服务器进程选择一个大于1024的端口号进行侦听,监听进程把这个端口号发回到客户端,要求客户端重新连接这个指定的端口。
  • 客户端重新连接监听指定的新端口,也就是重新进行连接。
  • 客户端与Server Process直接对话,不再通过监听,进行会话认证(登录),执行SQL等等。

从上述过程可以看到,客户端最终连接的端口实际上并不是1521。由于防火墙一般只开放了几个端口,对Oracle数据库只开放了1521端口,这样在客户端进行第二次连接时,不能通过防火墙,导致连接数据库失败。

值得庆幸的是,只有Windows平台上的9i及以下版本的Oracle才会有这个问题。Oracle在Linux以及Unix平台下,多个进程间可以对端口进行复用,Oracle Server Process仍然使用的是跟监听进程一个端口(1521)。通过在linux使用strace跟踪客户端连接数据库的过程可以发现,客户端只连接了一次,并没有进行第二次连接,与上面描述的流程相比已经发生了变化。在Windows平台上,10g及以上版本的库,也同样利用端口复用,避免了这样的问题。

那么Windows上运行的Oracle 9i怎么解决这个问题呢?答案很简单,在Windows注册表的\HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\HOMEn(这里n指Oracle Home的序号,只有一个Oracle Home时是0)键下面增加一项USE_SHARED_SOCKET,其值为TRUE。然后重启监听及Oracle服务(注意要重启Oracle的服务,而不仅仅是重启数据库),就可以解决此问题。实际上10g就是默认USE_SHARED_SOCKET为TRUE。

对于这种问题,或者是让防火墙打开针对数据库主机的所有端口访问,也能解决。但是这种方案往往会被负责安全的人否决。

下面这个由防火墙导致的问题,就相对复杂一点了。
Read the rest of this entry »

TAF PartIII

Posted by 老熊 on 4月 1st, 2009

前两篇文章(TAF PartITAF PartII)主要描述了在TAF发生故障转移时正在执行SELECT时的行为。本文将观察当故障转移发生时如果正在执行DML和DDL语句的行为。

本文将继续使用上一篇文章TAF PartII同样的测试环境。

我们先测试当会话update时,在另一个会话立即start force实例:

SQL> update t1 set object_name=lpad(’x',40,’x'),created=sysdate;
update t1 set object_name=lpad(’x',40,’x'),created=sysdate
*
ERROR 位于第 1 行:
ORA-25408: 无法安全重放调用

SQL> select * from dual;

D
-
X

SQL> select failed_over from v$session where sid=(select sid from v$mystat where rownum=1);

FAI
---
YES

可以看到,在数据库实例重启完成后,会话报“ORA-25408: 无法安全重放调用”错误。为什么会报这个错。我的猜测是,由于在FAILOVER后,会话实际上是重新来执行这个SQL,但是DML语句不像SELECT一样,前者是对当前时间点的数据进行修改,保证数据修改是最新的;而SELECT的机制是保证查询的时间点一致,DML语句在重新执行时,当前时间点的数据已经跟上次执行时的数据可能不相同了,不能保证是相同的,也就是“不安全”的。

从上面的输出可以看到,在会话报错之后,仍然可以执行查询,并且从结果来看表明会话是failover后的新会话。注意,ORA-25408这个错误是在failover之后才报的,并不是数据库实例DOWN下后马上就报错。

如果我们在数据库实例级别开启sql trace,在failover后我们可以从trace文件里面可以发现,UPDATE语句并没有再次被解析和执行,而不是在UPDATE重新执行然后报错。

下面我们来看看DDL语句的情况:
Read the rest of this entry »


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