两种原因:
- 一致读 ,CR (consistency Read)
查询修改数据的前镜像时,前景镜像被覆盖。 - 块延迟清理
事务工作步骤
Transaction 开始前 回滚段获取一个ITL(事务槽),分配空间, 记录事务信息
Transaction 提交后,redo完成记录,同时还清除回滚段的事务信息 包括行级锁,ITL信息(commit 标志,SCN等)
清除这些事务段的信息的过程就叫做 块清除, 在完成块清除时, 我们本事务修改的数据块就会存在两种可能
(1) 所有的数据块还保存在 buffer cache 中,
(2)部分数据块或者是全部数据块由于LRU管理已经被刷出了buffer cache。oracle为了考虑到块清除的成本,以及性能,会作以下两种方式的块清除处理:
快速块清除(fast block cleanout), 当事务修改的数据库全部保存在buffer cache 并且修改数据块的数据量没有超过 cache buffer 的 10%,快速清除事务信息。
延迟块清除(delayed block cleanout) 当修改的数据块的阀值超过10% 或者本次事务相关的数据块已经被刷出了 buffer cache, oracle 会下次访问此block 时再清除事务信息。
接下来,下一个select(读者)去读延迟块。
- 如果select 可以找到回滚段中记录的commit 时的SCN,那么延迟块正常清除,出现select产生redo的情况。
- 如果select 找不到commit时的SCN,证明需要清理commit的已经被回收。顺序为:
需要清理的scn(已被回收) < 能找到的最小事务的scn
这时候就需要分析现在select的SCN 在哪个位置:
2.1: select的SCN > 回滚段中最小的scn,及在最小scn之后, 那么完成块清理。
2.2 :select的SCN < 回滚段中最小的scn, 就是还是小于最小的scn 即还是找不到。就发生ORA-01555. oracle 会做设置需要清理的scn(已被回收) = 能找到的最小事务的scn。
++++++++++++++++++++++++
下面简单讲下ORA-1555的处理方法:
在磁盘空间不充足,可以调整undo_retention
SELECT d.undo_size/(1024*1024) "ACTUAL UNDO SIZE [MByte]",
SUBSTR(e.value,1,25) "UNDO RETENTION [Sec]",
ROUND((d.undo_size / (to_number(f.value) *
g.undo_block_per_sec))) "OPTIMAL UNDO RETENTION [Sec]"
FROM (
SELECT SUM(a.bytes) undo_size
FROM v$datafile a,
v$tablespace b,
dba_tablespaces c
WHERE c.contents = 'UNDO'
AND c.status = 'ONLINE'
AND b.name = c.tablespace_name
AND a.ts# = b.ts#
) d,
v$parameter e,
v$parameter f,
(
SELECT MAX(undoblks/((end_time-begin_time)*3600*24))
undo_block_per_sec
FROM v$undostat
) g
WHERE e.name = 'undo_retention'
AND f.name = 'db_block_size';
在磁盘充足,保证undo_retention不变,调整undo_size
SELECT d.undo_size/(1024*1024) "ACTUAL UNDO SIZE [MByte]",
SUBSTR(e.value,1,25) "UNDO RETENTION [Sec]",
(TO_NUMBER(e.value) * TO_NUMBER(f.value) *
g.undo_block_per_sec) / (1024*1024)
"NEEDED UNDO SIZE [MByte]"
FROM (
SELECT SUM(a.bytes) undo_size
FROM v$datafile a,
v$tablespace b,
dba_tablespaces c
WHERE c.contents = 'UNDO'
AND c.status = 'ONLINE'
AND b.name = c.tablespace_name
AND a.ts# = b.ts#
) d,
v$parameter e,
v$parameter f,
(
SELECT MAX(undoblks/((end_time-begin_time)*3600*24))
undo_block_per_sec
FROM v$undostat
) g
WHERE e.name = 'undo_retention'
AND f.name = 'db_block_size'