Archive

Archive for the ‘oracle 10g’ Category

重现ORA-24816

July 15th, 2009

在oracle 10.2版本中,ORA-24816是一个常见的错误,错误描述如下:

24816, 00000, “Expanded non LONG bind data supplied after actual LONG or LOB column”
// *Cause: A Bind value of length potentially > 4000 bytes follows binding for
// LOB or LONG.
// *Action: Re-order the binds so that the LONG bind or LOB binds are all
// at the end of the bind list.

解决办法很简单,如Action部分所描述的,将LONG和LOB得字段放在绑定列表的后面。

前两天有开发人员问如何解释Cause部分中的“potentially > 4000 bytes”呢?我写了一小段程序来重现这个错误:

首先我建了一个temp表,两个字段x CLOB和Y varchar2(4000)

用Perl写了两段程序:

首先是不正确的bind次序,LOB字段在varchar的前面

cat bad.pl
#!/usr/local/bin/perl

use DBI qw(:sql_types);
use Getopt::Std;
use Data::Dumper;

my $username=shift;
my $passwd= shift;
my $sid=shift;

$primary_dbh=DBI->connect(“dbi:Oracle:$sid”,$username,$passwd) || die “Can not connect to database.”;
$primary_dbh->{LongReadLen}=1024*1024*5;

$insert_sql = qq { insert into temp (
x,
y
)
values (?,?) };

$x=&lpad(“a”,”a”,10000);
$y=&lpad(“a”,”a”,4000);

$insert_sth = $primary_dbh->prepare($insert_sql);

$insert_sth->bind_param( 1, $x, SQL_LONGVARCHAR );
$insert_sth->bind_param( 2, $y);
$insert_sth->execute();

$primary_dbh->disconnect();
print “1 rows has been inserted into table\n”;
sub lpad {
#usage: &lpad(“a”,”a”, 1000000);
return($_[1] x ($_[2] – length($_[0])) . $_[0]);
}

然后是正确的Bind次序,将LOB字段放到了最后

cat good.pl
#!/usr/local/bin/perl

use DBI qw(:sql_types);
use Getopt::Std;
use Data::Dumper;

$username=shift;
$passwd= shift;
$sid=shift;

$primary_dbh=DBI->connect(“dbi:Oracle:$sid”,$username,$passwd) || die “Can not connect to database.”;
$primary_dbh->{LongReadLen}=1024*1024*5;

$insert_sql = qq { insert into temp (
y,
x
)
values (?,?) };

$x=&lpad(“a”,”a”,10000);
$y=&lpad(“a”,”a”,4000);

$insert_sth = $primary_dbh->prepare($insert_sql);

$insert_sth->bind_param( 1, $y);
$insert_sth->bind_param( 2, $x, SQL_LONGVARCHAR );
$insert_sth->execute();

$primary_dbh->disconnect();
print “1 rows has been inserted into table\n”;
sub lpad {
#usage: &lpad(“a”,”a”, 1000000);
return($_[1] x ($_[2] – length($_[0])) . $_[0]);
}

如果环境变量的NLS_LANG和数据库的NLS_CHARACTERSET一致的时候,这两个脚本都是没有问题的:

select * from v$nls_parameters where parameter =’NLS_CHARACTERSET’;
PARAMETER VALUE
—————- —–
NLS_CHARACTERSET UTF8
!env |grep NLS_LANG
NLS_LANG=AMERICAN_AMERICA.UTF8

> good.pl username password tnsname
1 rows has been inserted into table
> bad.pl username password tnsname
1 rows has been inserted into table

但是如果我们将NLS_LANG改为AMERICAN_AMERICA.WE8ISO8859P1,bad.pl将报错
bad.pl username password tnsname
DBD::Oracle::st execute failed: ORA-24816: Expanded non LONG bind data supplied after actual LONG or LOB column (DBD ERROR: error possibly near <*> indicator at char 14 in ‘ insert into <*>temp (
x,
y
)
values (:p1,:p2) ‘) [for Statement " insert into temp (
x,
y
)
values (?,?) " with ParamValues: :p1='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...', :p2='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...'] at bad.pl line 27.

good.pl 没有问题
good.pl username password tnsname
1 rows has been inserted into table

当我把$y=&lpad(“a”,”a”,4000);改为$y=&lpad(“a”,”a”,1333);时bad.pl执行成功,改为$y=&lpad(“a”,”a”,1334)后又不行了。

原因就是UTF8为三字节字符集,而WE8ISO8859P1为单字节字符集,因为oracle不知道里面是不是有特殊字符,所有将1334*3=4002 > 4000

Cause部分中的“potentially > 4000 bytes”就是这个意思。client和server端字符集不同只是其中的一种情况,还有其他的原因可能会导致这一情况。

解决办法就如good.pl中所示,将LOB,LONG字段放在最后就可以了。

Eagle Fan oracle 10g

尽量缩短oracle upgrade时间

July 1st, 2009

在做oracle upgrade的时候,因为需要down机,所以节约每一分钟时间都很重要,特别是当需要升级很多台数据库的时候。

我们过去两年的时间内升级了两三百台primary数据库。从节约时间上来说,大致有以下三个tips:

1. 将新的binary copy到服务器上,在维护前先relink all。
2. 在中间startup upgarde的时候采用比较小的SGA参数,这样可以节约allocate和deallocate SGA的时间,特别是对于SGA较大的情况(>20GB)
3. set echo off
这里不仅仅指运行catupgrd.sql前的set echo off,而且要修改两个脚本,将其中的set echo on改为set echo off或者去掉。

\mv $ORACLE_HOME/rdbms/admin/prvthlrt.plb $ORACLE_HOME/rdbms/admin/prvthlrt.plb.bak
sed ‘s/SET ECHO ON/SET ECHO OFF/’ $ORACLE_HOME/rdbms/admin/prvthlrt.plb.bak > $ORACLE_HOME/rdbms/admin/prvthlrt.plb

\mv $ORACLE_HOME/rdbms/admin/catocm.sql $ORACLE_HOME/rdbms/admin/catocm.sql.bak
sed ‘s/set echo on/set echo off/’ $ORACLE_HOME/rdbms/admin/catocm.sql.bak > $ORACLE_HOME/rdbms/admin/catocm.sql

改动这两个脚本可以节约好几分钟的时间。在不同的机器上时间差别可能不同。

我在Sun V440上做过9i到10g的实验,如果不修改文件,需要耗时27分钟,修改后耗时22分钟,相差5分钟。

Eagle Fan oracle 10g

如何安全的split partition

June 25th, 2009

在split partition时,如果所有索引都是local index并且新split出来的partition为空时,oracle不需要rebuild index,这就是fast split。

但是当新的partition非空时,则index处于unusable状态,需要rebuild index。而在rebuild index这段时间内,如果有SQL进来,就会出问题了。

oracle并没有给出一个安全的split partition的方法,比方说如果不能做fast split则不要做split。而unusable index对于7*24系统是非常危险的。

当然你可以在split的时候去跟踪,一旦发现写入新的segment,则取消操作,但是这样还是有风险。而且一般都是定期做split,通过脚本实现,不太可能每次都手工做。

要实现fast split关键是找到正确的split point。一般我们首先跑一个查询,找出当前这个partition的最大partition key,然后再加一就是split point,然后根据这个split point来做split。但是这中间有一个时间间隔,如果这中间有新的数据插入的话,split point就不对了。

对于partition key为一个列的分区表来说,查询可以走index range(max) scan,这个时间间隔很短,可能遇到该问题的概率很小,但是如果partition key为多列的话,只能走fast full index scan,这个时间间隔可能很长,风险大大增加了。

为了避免这一风险,我采用了下面的方法:

1. 查询将要做split的partition的最大partition_key
select $sql_hint max(Partition_key+1) SPLITPOINT from $tablename partition(${maxpartname})
2. 以exclusive模式lock partition
lock table $tablename partition(${maxpartname}) in EXCLUSIVE mode
3. 再次查询要做split的partition的最大partition_key
select $sql_hint max(Partition_key+1) SPLITPOINT from $tablename partition(${maxpartname})
4. 如果step1和step3得到的结果相同,则跳到step6,不同则跳到step5
5. 提交transaction,释放lock,等待30分钟后跳到step1
6. 查看v$lock表看自己有没有block别人,如果有,跳到step5,如果没有跳到step7
7. split the partition, 如果成功,退出程序,如果失败,跳转到step5

通过上面的方法就可以保证中间没有其他的transaction去做DML。

除了保证安全外,当失败时,该程序可以sleep一段时间后重试。

Eagle Fan oracle 10g

row-level dependency tracking (10g new feature)

April 7th, 2009

在10g版本中加入了row-level dependency tracking的新特性,默认是关闭的。

一般情况下,一个block中只有一个SCN号来记录整个block里的变化,打开该特性后每一行都有一个SCN号。

在建表时指定ROWDEPENDENCIES属性可以打开这一功能,我们来看下面的测试:

SQL> select count(*) from test;

COUNT(*)
———-
0

SQL> insert into test select rownum from dual connect by level <11;

10 rows created.

– ORA_ROWSCN 为 Pseudocolumn , 10g中引入

SQL> SELECT ORA_ROWSCN,rowid from test;

ORA_ROWSCN ROWID
———- ——————
205810221 AAAGinAABAAAGnxAAA
205810221 AAAGinAABAAAGnxAAB
205810221 AAAGinAABAAAGnxAAC
205810221 AAAGinAABAAAGnxAAD
205810221 AAAGinAABAAAGnxAAE
205810221 AAAGinAABAAAGnxAAF
205810221 AAAGinAABAAAGnxAAG
205810221 AAAGinAABAAAGnxAAH
205810221 AAAGinAABAAAGnxAAI
205810221 AAAGinAABAAAGnxAAJ

10 rows selected.

SQL> update test set x=100 where x=1;

1 row updated.

SQL> commit;

Commit complete.

SQL> SELECT ORA_ROWSCN,rowid from test;

ORA_ROWSCN ROWID
———- ——————
205810234 AAAGinAABAAAGnxAAA
205810234 AAAGinAABAAAGnxAAB
205810234 AAAGinAABAAAGnxAAC
205810234 AAAGinAABAAAGnxAAD
205810234 AAAGinAABAAAGnxAAE
205810234 AAAGinAABAAAGnxAAF
205810234 AAAGinAABAAAGnxAAG
205810234 AAAGinAABAAAGnxAAH
205810234 AAAGinAABAAAGnxAAI
205810234 AAAGinAABAAAGnxAAJ

10 rows selected.

开启该功能后:

SQL> drop table test;

Table dropped.

SQL> create table test(x int) ROWDEPENDENCIES;

Table created.

SQL> insert into test select rownum from dual connect by level <11;

10 rows created.

SQL> commit;

Commit complete.

SQL> SELECT ORA_ROWSCN,rowid from test;

ORA_ROWSCN ROWID
———- ——————
205810275 AAAGm8AABAAAGnxAAA
205810275 AAAGm8AABAAAGnxAAB
205810275 AAAGm8AABAAAGnxAAC
205810275 AAAGm8AABAAAGnxAAD
205810275 AAAGm8AABAAAGnxAAE
205810275 AAAGm8AABAAAGnxAAF
205810275 AAAGm8AABAAAGnxAAG
205810275 AAAGm8AABAAAGnxAAH
205810275 AAAGm8AABAAAGnxAAI
205810275 AAAGm8AABAAAGnxAAJ

10 rows selected.

SQL> update test set x=100 where x=1;

1 row updated.

SQL> commit;

Commit complete.

SQL> SELECT ORA_ROWSCN,rowid from test;

ORA_ROWSCN ROWID
———- ——————
205810284 AAAGm8AABAAAGnxAAA
205810275 AAAGm8AABAAAGnxAAB
205810275 AAAGm8AABAAAGnxAAC
205810275 AAAGm8AABAAAGnxAAD
205810275 AAAGm8AABAAAGnxAAE
205810275 AAAGm8AABAAAGnxAAF
205810275 AAAGm8AABAAAGnxAAG
205810275 AAAGm8AABAAAGnxAAH
205810275 AAAGm8AABAAAGnxAAI
205810275 AAAGm8AABAAAGnxAAJ

10 rows selected.

得到SCN号后,可以通过SCN_TO_TIMESTAMP function来得到timestamp,误差为3s以内

SQL> select SCN_TO_TIMESTAMP(205810284) from dual;

SCN_TO_TIMESTAMP(205810284)
—————————————————————————
07-APR-09 12.49.18.000000000 AM

SQL> select SCN_TO_TIMESTAMP(205810275) from dual;

SCN_TO_TIMESTAMP(205810275)
—————————————————————————
07-APR-09 12.48.57.000000000 AM

当然该特性需要少量额外的空间(每行6 bytes)以及少量额外的CPU消耗,在正式环境中可能对于某些特殊的应用有实用价值。

Eagle Fan oracle 10g

high US enqueue

September 9th, 2008

前两天在一个生产库上出现了大量的US enqueue. 该系统版本为10203,采AUM管理模式。

因为事情发生在美国的白天,所以只能通过一些statspack中的信息去追溯根本原因。

第一感觉觉得可能是online undo segment时产生的US enqueue,这个问题在9205的数据库特别繁忙时频频出现。

因为AUM的管理方式是当系统不是很忙时会offline一些undo segments,而当不够用时,再将其online,不过当系统特别繁忙时在online或者resize时就会出现问题,可以通过10511来解决这个问题,详细信息可以参考我的另外一片文章:10511 event

但奇怪的地方在于当时该数据库并不是特别繁忙,并不是在波峰时段。当查看stats$undostat时,一个参数吸引了我的注意力:UNXPSTEALCNT

我们来看看UNXPSTEALCNT的解释:

UNXPBLKREUCNT: Number of unexpired undo blocks reused by transactions

一般来说该参数都应该等于0,但是这个参数在那段时间是大于0的。因为只有当undo tablespace不够存放undo_retention时间段内的数据的时候,才会发生unexpired undo extents stealing。再去查看stats$rollstat,发现但是RSSIZE和undo tablespace大小是一样的,这就说明当时undo tablespace确实不够用了。

那么为什么在系统不是很繁忙的时候会出现undo不够用的情况呢,如果说不够用,那在波峰时段应该问题更加严重才对。

查看stats$undostat.tuned_undoretention参数发现了问题所在。从10.2版本开始,oracle默认采用自动调整undo retention的方法,根据你undo tablespace的大小以及系统的繁忙程度(v$undostat中信息)自动调整undo_retention参数,所以在10g的数据库上你会经常发现undo tablespace永远是满的,因为当你undo tablespace有空闲空间时,系统自动调大undo_retention来保留更多的undo blocks。这一方法有利于时间长的查询,但是对于典型的OLTP系统来说不太适用,因为OLTP上不太可能跑如此长时间的查询,而且在很繁忙的OLTP上还会导致上面所遇到的问题。oracle真是吃力不讨好。

出问题前一天,数据库做维护被重启过,因为刚起来数据库很空闲,所以v$undostat.tuned_autoretention很大,undo tablespace被撑满,虽然tuned_autoretention一直在降,但是还是没有赶上系统warm up的速度,导致数据库出现了问题。

该功能可以通过_undo_autotune参数被disable,disable后v$undostat不在更新。

_undo_autotune : enable auto tuning of undo_retention

该参数可以在线修改:

alter system set “_undo_autotune” = false;

Eagle Fan oracle 10g