2009年1月13日星期二

关于ORA_ROWSCN的一点问题

作者:江枫 | 【转载时请务必以超链接形式标明文章原始出处和作者信息及本声明】
地址:http://rdc.taobao.com/blog/dba/html/243_about_ora_rowscn.html

以下是引文:

Oracle10g引入了一个新的ORA_ROWSCN的伪列,可以查询表中记录最后变更的SCN。这个新的伪列在某些环境下会非常有用,比如执行乐观锁定,或者增量数据抽取的时候。但是,默认情况下,每行记录的ORA_ROWSCN是基于Block的,除非在建表的时候执行开启行级跟踪(create table … rowdependencies)。

先来简单理解一下ORA_ROWSCN的实现原理。我们知道,每个Block在头部是记录了该block最近事务的SCN的,所以默认情况下,只需要从block头部直接获取这个值就可以了,不需要其他任何的开销,Oracle就能做到这一点。但是这明显是不精确的,一个block中会有很多行记录,每次事务不可能影响到整个block中所有的行,所以这是一个非常不精准的估算值,同一个block的所有记录的ORA_ROWSCN都会是相同的,基本上没有多大的使用价值。

如果在建表的时候开启行级跟踪选项,Oracle则可以为每一行记录精确的SCN,那么显然不能再直接从block头部获取。要获得足够的信息,肯定要付出一定的代价,Oracle必须为每一行实际的存储这个SCN。所以这个行级跟踪的选项,只能在建表的时候指定,而不能通过alter table来修改现有的表,否则需要修改每一行记录的物理存储格式,代价是可想而知的。

简单的做个实验就可以知道开启行级跟踪以后块记录格式的不同。建两个表,一个norowdependencies(默认),一个rowdependencies,然后分别dump出相应的数据块:

create table t1(i int);
insert into t1 values(1);
insert into t1 values(2);
commit;
create table t2 rowdependencies as select * from t1;

norowdependencies:

block_row_dump:
tab 0, row 0, @0×1f9a
tl: 6 fb: –H-FL– lb: 0×1 cc: 1
col 0: [ 2] c1 02
tab 0, row 1, @0×1f94
tl: 6 fb: –H-FL– lb: 0×1 cc: 1
col 0: [ 2] c1 03
end_of_block_dump

rowdependencies:

block_row_dump:
tab 0, row 0, @0×1f7c
tl: 12 fb: –H-FL– lb: 0×0 cc: 1
dscn 0×0000.00029ae4
col 0: [ 2] c1 02
tab 0, row 1, @0×1f6d
tl: 12 fb: –H-FL– lb: 0×0 cc: 1
dscn 0×0000.00029ae4
col 0: [ 2] c1 03
end_of_block_dump

得到行的SCN后,通过SCN_TO_TIMESTAMP函数可以转化为时间:

SQL> select SCN_TO_TIMESTAMP(ora_rowscn) from t2 where rownum<2;

SCN_TO_TIMESTAMP(ORA_ROWSCN)
---------------------------------------------------------------------------
06-JAN-09 05.31.20.000000000 PM

乐观锁和ORA_ROWSCN
需要select … for update做悲观锁定的时候,通过使用ORA_ROWSCN可以改成乐观锁定。一开始select数据的时候将ORA_ROWSCN查出来,修改后如果要写回数据库之前再比对下最新的ORA_ROWSCN就可以知道这期间数据是否有发生变化。这个Tom在他的大著《Expert Oracle Database Architecture 9i and 10g Programming Techniques and Solutions 》中也是有提到的。

增量数据抽取和ORA_ROWSCN
每次抽取后记录最大的ORA_ROWSCN,下次抽取再基于上一次的SCN来获得最近修改过的数据即可。在10g之前,很多系统要实现增量数据抽取,要么通过解析日志,要么加触发器,要么就在表上加一个时间截字段。ORA_ROWSCN其实就是第三种方式,只是这个字段由Oracle来维护,这样可以避免一些应用绕过时间截去更新其他字段带来的问题。不过,如果系统中使用了逻辑备库或者streams等逻辑复制的方案,而数据抽取又是基于逻辑备库的话,ORA_ROWSCN就可能对抽取后的数据分析有影响了,因为通过这个得到的时间是逻辑备库上记录变更的时间,而不是源库的时间了。当然,如果纯粹只是做数据抽取,而不需要使用这个时间来做分析,还是问题不大的,但还是要考虑一旦逻辑备库出现故障需要重做的,则这个增量抽取要怎么来处理的问题。

Metalink上搜一下ORA_ROWSCN可以看到不少相关的bug,所以在生产系统中使用的时候要小心。例如,我在Linux64平台上的一台测试库中,执行以下语句就会碰到ORA-07445的错误:

SQL> select ora_rowscn from x$bh where rownum<2;
select ora_rowscn from x$bh where rownum<2
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

ORA-07445: exception encountered: core dump [qkafix()+212]
[SIGSEGV] [Address not mapped to object] [0x000000004] [] []

以下是自己的观点和测试:

自己觉得,其实不光在OLAP系统里有问题,在OLTP系统里也照样有问题,会增加SQL执行的次数。在系统默认的块级SCN列情况下,只要修改同一个块里的任意一条数据,就会导致其他的操作落空,而用户必需重新进行其操作,因此会大幅度的增加SQL的执行次数,特别是在热点数据块比较集中的时候,会产生很大的性能问题。而如果是行级的SCN列的情况下,就不会有这个问题存在,但是正如作者所说,“要获得足够的信息,肯定要付出一定的代价,Oracle必须为每一行实际的存储这个SCN”,说白了,行级SCN列还是一种以空间换取时间的做法。

举个例子说:
create table t1(i number) pctfree 99;
insert into t1 select rownum from all_objects where rownum<=100;
commit;
create table t2 rowdependencies as select * from t1;

fy@ORCL> select rowid,i,ora_rowscn from t1 where rownum<=20 order by i;

ROWID I ORA_ROWSCN
------------------ ---------- ----------
AAARC+AAFAAAAAZAAA 64 970530
AAARC+AAFAAAAAZAAB 65 970530
AAARC+AAFAAAAAZAAC 66 970530
AAARC+AAFAAAAAZAAD 67 970530
AAARC+AAFAAAAAZAAE 68 970530
AAARC+AAFAAAAAZAAF 69 970530
AAARC+AAFAAAAAZAAG 70 970530
AAARC+AAFAAAAAaAAA 71 970530
AAARC+AAFAAAAAaAAB 72 970530
AAARC+AAFAAAAAaAAC 73 970530
AAARC+AAFAAAAAaAAD 74 970530
AAARC+AAFAAAAAaAAE 75 970530
AAARC+AAFAAAAAaAAF 76 970530
AAARC+AAFAAAAAaAAG 77 970530
AAARC+AAFAAAAAbAAA 78 970530
AAARC+AAFAAAAAbAAB 79 970530
AAARC+AAFAAAAAbAAC 80 970530
AAARC+AAFAAAAAbAAD 81 970530
AAARC+AAFAAAAAbAAE 82 970530
AAARC+AAFAAAAAbAAF 83 970530

20 rows selected.

fy@ORCL> select rowid,i,ora_rowscn from t2 where rownum<=20 order by i;

ROWID I ORA_ROWSCN
------------------ ---------- ----------
AAARC/AAFAAAAAtAAA 64 970587
AAARC/AAFAAAAAtAAB 65 970587
AAARC/AAFAAAAAtAAC 66 970587
AAARC/AAFAAAAAtAAD 67 970587
AAARC/AAFAAAAAtAAE 68 970587
AAARC/AAFAAAAAtAAF 69 970587
AAARC/AAFAAAAAtAAG 70 970587
AAARC/AAFAAAAAtAAH 71 970587
AAARC/AAFAAAAAtAAI 72 970587
AAARC/AAFAAAAAtAAJ 73 970587
AAARC/AAFAAAAAtAAK 74 970587
AAARC/AAFAAAAAtAAL 75 970587
AAARC/AAFAAAAAtAAM 76 970587
AAARC/AAFAAAAAtAAN 77 970587
AAARC/AAFAAAAAtAAO 78 970587
AAARC/AAFAAAAAtAAP 79 970587
AAARC/AAFAAAAAtAAQ 80 970587
AAARC/AAFAAAAAtAAR 81 970587
AAARC/AAFAAAAAtAAS 82 970587
AAARC/AAFAAAAAtAAT 83 970587

20 rows selected.

假设现在有一个用户想要更新t1表的ROWID为AAARC+AAFAAAAAZAAA的数据,他运行了这条语句:
fy@ORCL> update t1 set i=10000 where rowid='AAARC+AAFAAAAAZAAA';

1 row updated.

fy@ORCL> commit;

Commit complete.

这时查询表里的数据如下:
fy@ORCL> select rowid,i,ora_rowscn from t1 where rownum<=20 order by i;

ROWID I ORA_ROWSCN
------------------ ---------- ----------
AAARC+AAFAAAAAZAAB 65 970638
AAARC+AAFAAAAAZAAC 66 970638
AAARC+AAFAAAAAZAAD 67 970638
AAARC+AAFAAAAAZAAE 68 970638
AAARC+AAFAAAAAZAAF 69 970638
AAARC+AAFAAAAAZAAG 70 970638
AAARC+AAFAAAAAaAAA 71 970530
AAARC+AAFAAAAAaAAB 72 970530
AAARC+AAFAAAAAaAAC 73 970530
AAARC+AAFAAAAAaAAD 74 970530
AAARC+AAFAAAAAaAAE 75 970530
AAARC+AAFAAAAAaAAF 76 970530
AAARC+AAFAAAAAaAAG 77 970530
AAARC+AAFAAAAAbAAA 78 970530
AAARC+AAFAAAAAbAAB 79 970530
AAARC+AAFAAAAAbAAC 80 970530
AAARC+AAFAAAAAbAAD 81 970530
AAARC+AAFAAAAAbAAE 82 970530
AAARC+AAFAAAAAbAAF 83 970530
AAARC+AAFAAAAAZAAA 10000 970638

20 rows selected.
我们可以看见块号为AAARC+AAFAAAAAZ的所有行的SCN都发生了变化,假设有另外一个用户同时也要更新ROWID为AAARC+AAFAAAAAZAAB的行,但是发起操作的时间稍微考后一点,虽然没有人修改这一行数据,但是由于第一个用户的修改操作,这时的SCN已经变化了,其操作会落空,那么该用户不得不重新进行一次操作,这显然不是我们想要的结果。

而反观行级SCN,
fy@ORCL> update t2 set i=10000 where rowid='AAARC/AAFAAAAAtAAA';

1 row updated.

fy@ORCL> commit;

Commit complete.
查询表里的数据:
fy@ORCL> select rowid,i,ora_rowscn from t2 where rownum<=20 order by i;

ROWID I ORA_ROWSCN
------------------ ---------- ----------
AAARC/AAFAAAAAtAAB 65 970587
AAARC/AAFAAAAAtAAC 66 970587
AAARC/AAFAAAAAtAAD 67 970587
AAARC/AAFAAAAAtAAE 68 970587
AAARC/AAFAAAAAtAAF 69 970587
AAARC/AAFAAAAAtAAG 70 970587
AAARC/AAFAAAAAtAAH 71 970587
AAARC/AAFAAAAAtAAI 72 970587
AAARC/AAFAAAAAtAAJ 73 970587
AAARC/AAFAAAAAtAAK 74 970587
AAARC/AAFAAAAAtAAL 75 970587
AAARC/AAFAAAAAtAAM 76 970587
AAARC/AAFAAAAAtAAN 77 970587
AAARC/AAFAAAAAtAAO 78 970587
AAARC/AAFAAAAAtAAP 79 970587
AAARC/AAFAAAAAtAAQ 80 970587
AAARC/AAFAAAAAtAAR 81 970587
AAARC/AAFAAAAAtAAS 82 970587
AAARC/AAFAAAAAtAAT 83 970587
AAARC/AAFAAAAAtAAA 10000 970673

20 rows selected.
可以看到只有最后一行的SCN列的值发生了变化,而这种情况下显然不会引起上述问题。

当然如果不想因为使用行级SCN带来存储空间的增长,我们也可以考虑将数据分布到不同的数据块上,这就需要DBA监测热点数据分布。

欢迎有问题讨论

没有评论: