这篇文章紧接着上一篇“ORA-01555错误总结(一)”,主要描述延迟块清除导致的ORA-01555错误。下面是一些简单介绍(上一遍已经介绍过) ORA-01555错误是一种在oracle
数据库中非常常见的错误,甚至也可以说是一个非常经典的错误,只是由于oracle的发展越来越自动化(UNDO自动管理+加强),这个错误已经越来越少见,可能很多使用10g的DBA都没有遇到过这个错误。 这个错误在9i之前的版本(UNDO手工管理)出现的最多,也是最常见的,甚至可以说怎么样处理和避免ORA-01555 错误是令每一个DBA曾头痛,但是又必须面对的问题。从9i的undo自动管理,至现在的10g、11g中的undo auto tuning,使得ORA-01555错误越来越少,但是这个错误仍然不可避免,特别是那些分析型的
系统中(OLTP)。
错误原因(一般有两种) SQL语句执行时,需要读取UNDO(前映像数据)来构造CR数据块,完成一致性读取。但是在读取undo前映像数据时发现,undo信息已经被覆盖(undo空间循环使用),不能构造一致性读的CR块,抛出ORA-01555错误SQL语句执行时,访问到的数据块,需要进行延迟块清除,但是在进行延迟块清除时,不能确定这个数据块的事务提交时间与SQL执行开始时间的先后次序,从而抛出ORA-01555错误 备注:延迟块清除是指前一个事务完成提交时(commit),由于修改块已经刷新至磁盘等原因,未完成块事务信息的清除(ILT,LB信息等等),在后续的SQL语句访问该块时,需要清除这些信息,这个动作即延迟块清除。
第二种情况的解决方法(仅供参考) 增加UNDO空间,延缓UNDO信息被覆盖,也可以理解为增加undo空间循环使用一次的时间,尽量避免延迟块清除时需要的undo信息被覆盖。优化抛出错误的SQL语句,减少SQL语句需要访问的数据块,可能避免出现ORA-01555,但是这个方法治标不治本,任何后续访问该块的SQL,都会抛出ORA-01555错误。加载buffer cache,尽量使事务提交时,能够完成fast commit(直接清理快上的事务信息),这个方法基本也是过于理想,很难在实际中发挥作用。缩减事务大小,也尽量使事务提交时,执行fast commit。由于需要改造业务逻辑,基本也不现实,很难在实际中发挥作用。
我们知道这类错误一般出现在OLAP类型的业务系统中,针对这种情况,最有效的方法是收集可能出现延迟块清除并抛出ORA-01555错误的表,在业务逻辑中,完成事务后,针对这些表立即进行一次全表扫描(清理块上的事务信息),避免后续访问清理时出现的ORA-01555错误。如果业务逻辑修改较困难,可以根据业务规则,指定一个定时针对这些表的全表扫面任务,来规避延迟块清除导致的ORA-01555错误 备注:针对第一情况的解决方法和示例请见我上一篇文章《ORA-01555错误总结(一)》。
注意事项 全表查询中使用select count(*),避免使用select *,因为select count(*)的效率和速度远远高于select *;必须使用提示(full hint),避免使用索引快速扫面的执行计划来统计行数(select count(*))不能使用并行提示(parallel),因为在表上进行并行查询是,以DIRECT READ方式读取表时不会清理数据块上的事务信息11GR2中新特性,当表大小超过一定阀值时,使用direct path read代替db file scatter read扫描,必须避免这种情况。如果表实在太大,全表扫描的时间过长,可以将表分段(根据dba_extends转化为rowid),进行分段查询。 全表扫描语法
select /*+
full(a) */
count(*)
from scott.emp a; 表分段语法(trunks变量表示将表分为几段,owner和table_name变量分别表示表对象的用户名和表名)(摘自老熊博客)
select dbms_rowid.rowid_create(1, oid1, fid1, bid1, 0) rowid1,
dbms_rowid.rowid_create(1, oid2, fid2, bid2, 9999) rowid2
from (
select a.*, rownum rn
from (
select chunk_no,
min(oid1) oid1,
max(oid2) oid2,
min(fid1) fid1,
max(fid2) fid2,
min(bid1) bid1,
max(bid2) bid2
from (
select chunk_no,
FIRST_VALUE(data_object_id) OVER(PARTITION
BY chunk_no
ORDER
BY data_object_id, relative_fno, block_id
ROWS
BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING) oid1,
LAST_VALUE(data_object_id) OVER(PARTITION
BY chunk_no
ORDER
BY data_object_id, relative_fno, block_id
ROWS
BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING) oid2,
FIRST_VALUE(relative_fno) OVER(PARTITION
BY chunk_no
ORDER
BY data_object_id, relative_fno, block_id
ROWS
BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING) fid1,
LAST_VALUE(relative_fno) OVER(PARTITION
BY chunk_no
ORDER
BY data_object_id, relative_fno, block_id
ROWS
BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING) fid2,
FIRST_VALUE(block_id) OVER(PARTITION
BY chunk_no
ORDER
BY data_object_id, relative_fno, block_id
ROWS
BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING) bid1,
LAST_VALUE(block_id + blocks - 1) OVER(PARTITION
BY chunk_no
ORDER
BY data_object_id, relative_fno, block_id
ROWS
BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING) bid2
from (
select data_object_id,
relative_fno,
block_id,
blocks,
ceil(sum2 / chunk_size) chunk_no
from (
select /*+
rule */ b.data_object_id,
a.relat |