oracle
NinGoo's blog

学习oci编程,从ociuldr开始

Update:发现原来在linux上写的代码,到windows上编译会有些错误,主要是编译器对变量声明的位置容忍度不一样,已经修正了代码。另外对输出的日志做了一点增强,打印了导出的总时间,省得人工计算了。并将源代码,windows32位版本,linux32位版,及linux64版本一并打包提供下载测试。

一直想学学oci编程,无奈基础不好,几次都是看了几页文档就放下了。所以比较佩服老楼,写了很多优秀的工具,例如广泛使用的ociuldr,以及为很多人解决了难题的AUL等。最近老楼焕发第二春,对ociuldr做了大量改进,并且升级成了sqluldr2,刚刚获得的最新消息是,sqluldr2开始支持gzip压缩格式了,牛人就是牛。

上周六一大早跑到公司,处理完一点小事后,想想反正来了,就多呆会儿吧。干什么呢?溜达到老楼的blog上,看到一对sqluldr的信息,就花了一上午的时间研究了下老版的ociuldr的源代码,再参考hrb_qiuyb写的oci8版的ociuldr2,这次终于能看明白了,还是oci8的函数比较容易懂,于是就开始自己动手编译。hrb_qiuyb版的bug比较多,动不动就segment fault,想了想不如多花点时间,好好的重新改写一下,虽然老楼已经出了sqludr2,珠玉在前,不过最近流行山寨,他做企业版,我来做山寨版,哈哈。山寨版的名字,就叫tbuldr(ToolBox*UnLoader),因为是基于老楼开放出来的源代码弄的,那就继续开源吧,希望不要抢了sqluldr2的生意哈。

到今天为止,代码终于差不多稳定了,修复了几个主要的bug,同时把sqluldr2最新的一些特性也用山寨的方法实现了,可能效率不如老楼的,不过至少外观上看是差不多了,如输出到stdout新版本的sqlldr控制文件固定长度导出指定表名导出按列输出等功能一一实现了。

另外还实现了一些小的功能:

  • feedback参数,指定输出多少行以后打印一条日志,可以更好的观察进度,默认还是50w条。
  • enclose参数,为每列加入包含字符。当选择的field字符在列值中有出现的时候,enclose就有作用了。
  • fixlen参数,表示固定长度输出。而不是像sqluldr2一样当field指定为0×20(空格)的时候采用固定长度。这个主要是考虑到某些非常特殊的情况下,可能会有人希望用空格做分隔符而又不希望是固定长度的。
  • 还有个隐含的debug参数,打开后会输出一些debug信息,主要array fetch时为各列分配的内存等数据。这个主要是dw用了老楼很早以前的一个debug版本,觉得log里输出了列名还挺有用,就保留了,不过默认是不显示的,也没有写在help里,这里偷偷的透露一下了,哈哈
$tbuldr

----------------------------------------------------------------------------
- tbuldr: Release 2.21 (ociuldr)
- First issued by Lou Fangxin (@) Copyright 2004/2008, all rights reserved.
- Modified by Ning Haiyuan (http://www.NinGoo.net).
----------------------------------------------------------------------------
 Usage: TBULDR keyword=value [,keyword=value,...]
 Valid Keywords:
       user     = username/password@tnsname
       query    = select statement, can simply speicify a table name
       sql      = SQL file name
       field    = seperator string between fields
       record   = seperator string between records
       enclose  = fields enclose string
       file     = output file name, default: uldrdata.txt
       head     = print row header(Yes|No,ON|OFF,1|0)
       read     = set DB_FILE_MULTIBLOCK_READ_COUNT at session level
       sort     = set SORT_AREA_SIZE at session level (UNIT:MB)
       hash     = set HASH_AREA_SIZE at session level (UNIT:MB)
       serial   = set _serial_direct_read to TRUE if 1 at session level
       trace    = set event 10046 to given level at session level
       table    = table name in the sqlldr control file
       mode     = sqlldr option, INSERT or APPEND or REPLACE or TRUNCATE
       log      = log file name, prefix with + to append mode
       long     = maximum long field size, default 8192 max 65534
       array    = array fetch size, default 50
       buffer   = sqlldr READSIZE and BINDSIZE, default 16 (MB)
       feedback = display progress every x rows, default 500000
       form     = display rows as form (Yes|No)
       fixlen   = fix length format (Yes|No)

  for field, record and enclose, use '0x' to specify hex character code
  r=0x0d n=0x0a |=0x7c ,=0x2c t=0x09  (more: man ascii)
$tbuldr user=test/test query=t
       0 rows exported at 2009-04-16 20:43:53
    400 rows exported at 2009-04-16 20:43:54
         output file uldrdata.txt closed at 4 rows. Elapsed time: 0 min 1 sec.
         control file is t_sqlldr.ctl.

当然,山寨版就是山寨版,以下sqluldr2的新功能暂未实现:

  • parfile,这个是比较有用功能,可以防止命令行中显式输入密码导致密码泄漏等。因为涉及到文件内容解析,对我来说,难度有点高,以后再说。
  • gzip压缩,这是下午老楼刚刚加入的新特性,还有待研究。不过这是利用了zlib实现的,网上有不少现成的代码,集成进来应当是能搞定的。另外也可以输出到stdout后通过管道调用gzip实现压缩,这个已经支持了。
  • BLOB/BFILE等不提供支持,虽然BLOB可以按照LONG RAW的方式导出成16进制字符,不过不保证没问题,如果BLOB里存储的是字符应当问题不大。CLOB/LONG最大只支持到65534字节,对于大部分情况应当是足够了。

第一次用c和oci正儿八经的写东西,bug肯定一大堆。山寨有风险,慎用,如果是生产环境有需求的,建议还是找老楼的企业版,支持山寨版的同学,可以自行编译帮忙做测试,有bug和建议欢迎提交^_^

ORA-00494与Oracle10.2.0.4

Oracle10.2.0.4中,如果数据库压力较大,IO发生短暂停止响应的时候,可能导致数据库实例崩溃。在alert中可以找到如下错误:

Thu Apr  2 10:55:36 2009
Errors in file /bdump/test.trc:
ORA-00494: enqueue [CF] held for too long (more than 900 seconds) by 'inst 1, osid 82866'
Thu Apr  2 10:55:39 2009
Killing enqueue blocker (pid=82866) on resource CF-00000000-00000000
 by terminating the process

ORA-00494或许还会伴随ORA-600 [2103]错误的出现,这是Oracle10.2.0.4中引入的一个行为改变。在Oracle10.2.0.4中,如果一个进程在指定的时间(默认900秒)内无法获得CF锁,则CF锁的执行进程会被kill。从Metalink上可以发现,CF enqueue相关的bug是相当的多,10.2.0.4这个行为的引入,估计是Oracle视图对这个问题做出解决的一种尝试。但是这种直接kill进程的激烈方式,肯定不是最好的方式,如果锁的持有者是后台进程,直接kill则很可能直接导致实例崩溃。

Metalink上对此的描述如下:

In 10.2.0.4 database alert log the following error appears:

ORA-00494: enqueue [CF] held for too long (more than 900 seconds)

This error can also be accompanied by ORA-600 [2103] which is basically the same problem -
a process was unable to obtain the CF enqueue within the specified timeout (default 900 seconds).

This behavior can be correlated with server high load and high concurrency on resources,
IO waits and contention, which keep the Oracle background processes from receiving the necessary
resources.

要避免该问题,最好当然是找出为什么会有进程持有CF这么长的时间。另外,也可以通过隐含参数禁止Oracle主动kill CF锁的持有进程。这个参数会禁止Oracle去kill所有持有CF锁的进程,不管是后台进程还是前台进程:

_kill_controlfile_enqueue_blocker=false

也可以通过设置另外一个隐含参数禁止Oracle去kill持有锁的后台进程:

_kill_enqueue_blocker=1(默认为3)

进程请求CF enqueue的time out时间默认是900秒,这是由下面的参数控制的:

_controlfile_enqueue_timeout=900

进程持有CF enqueue的时间最长不能超过120秒:

_controlfile_enqueue_holding_time=120

exp遭遇EXP-00008 ORA-04068 ORA-04067 ORA-06508

今天在full exp一个库的时候碰到了错误:

EXP-00008: ORACLE error 4068 encountered
ORA-04068: existing state of packages has been discarded
ORA-04063: package body "SYS.LT_EXPORT_PKG" has errors
ORA-06508: PL/SQL: could not find program unit being called
ORA-06512: at line 1
EXP-00083: The previous problem occurred when calling SYS.LT_EXPORT_PKG.system_info_exp
EXP-00008: ORACLE error 6502 encountered

查看系统,发现这个包是的package body部分是invalid的

SQL> select owner,object_name,object_type,status from dba_objects
where object_name = 'LT_EXPORT_PKG'; 

OWNER      OBJECT_NAME          OBJECT_TYPE        STATUS
---------- -------------------- ------------------ -------
SYS        LT_EXPORT_PKG        PACKAGE            VALID
SYS        LT_EXPORT_PKG        PACKAGE BODY       INVALID

尝试用?/rdbms/admin/utlrp重新编译,还是无效。查了一下metalink,发现这个问题还不少。LT_EXPORT_PKG是9i新引入的owm特性(Oracle Database Workspace Manager,参考Metalink:156963.1)的一个包,其属主为sys,但是到了10g,又改到了wmsys下,所以如果系统中安装了该组件,在升级的时候还要注意检查下。最后没办法,只好重建该package搞定:

@?/rdbms/admin/owminst.plb

所以说,对于数据库,一些不必要的组件能不装就不要装,一些不必要的特性,能不用就不用,免得带来一些意想不到的麻烦,影响工作效率,甚至造成不必要的宕机时间。

如何根据rowid获取extent_id

我们知道,rowid是由四部分组成的,分别是data_object_id,file_id,block_number和row_number,通过oracle提供的dbms_rowid包可以很方便的将一串rowid解析出上述四部分的内容。然后根据这些信息,则可以获取其extent_id。

SYS@datac>declare
  2  v_block_id number;
  3  v_file_id number;
  4  v_object_id number;
  5  v_extent_id number;
  6  v_object_name varchar2(30);
  7  v_owner varchar2(30);
  8  v_rowid varchar2(20):='AAACrKAAXAAAAzUAAH';
  9  begin
 10  select dbms_rowid.ROWID_BLOCK_NUMBER(v_rowid),
 11         dbms_rowid.ROWID_RELATIVE_FNO(v_rowid),
 12         dbms_rowid.ROWID_OBJECT(v_rowid)
 13   into v_block_id,v_file_id,v_object_id
 14  from dual;
 15
 16  select owner,object_name
 17    into v_owner,v_object_name
 18  from dba_objects
 19  where data_object_id=v_object_id;
 20
 21  select extent_id into v_extent_id
 22  from dba_extents
 23  where owner=v_owner
 24  and segment_name=v_object_name
 25  and file_id=v_file_id
 26  and v_block_id between block_id and block_id+blocks-1;
 27
 28  dbms_output.put_line('         rowid: '||v_rowid);
 29  dbms_output.put_line('       file_id: '||v_file_id);
 30  dbms_output.put_line('      block_id: '||v_block_id);
 31  dbms_output.put_line('data_object_id: '||v_object_id);
 32  dbms_output.put_line('         owner: '||v_owner);
 33  dbms_output.put_line('   object_name: '||v_object_name);
 34  dbms_output.put_line('     extent_id: '||v_extent_id);
 35  end;
 36  /
         rowid: AAACrKAAXAAAAzUAAH
       file_id: 23
      block_id: 3284
data_object_id: 10954
         owner: NINGOO
   object_name: TEST
     extent_id: 5

将上述代码打包到一个shell脚本中,rowid通过参数传入,则可以更方便日常环境中使用。工欲善其事,必先利其器,将经验转化为工具,利用工具提升效率,才能做一个Lazy DBA

$ tbsql rowid AAACrKAAXAAAAzUAAH
         rowid: AAACrKAAXAAAAzUAAH
       file_id: 23
      block_id: 3284
data_object_id: 10954
         owner: NINGOO
   object_name: TEST
     extent_id: 5

$ tbsql rowid AAACrKAAZAAABiiAAR
         rowid: AAACrKAAZAAABiiAAR
       file_id: 25
      block_id: 6306
data_object_id: 10954
         owner: NINGOO
   object_name: TEST
     extent_id: 7