程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> Oracle數據庫 >> Oracle教程 >> OracleORA-01555快照過舊說明

OracleORA-01555快照過舊說明

編輯:Oracle教程

OracleORA-01555快照過舊說明


ORA-01555 快照過舊,是數據庫中很常見的一個錯誤,比如當我們的事務需要使用undo來構建CR塊的時候,而此時對應的undo 已經不存在了, 這個時候就會報ORA-01555的錯誤。

ORA-01555錯誤在Oracle 8i及之前的版本最多。從9i開始的undo自動管理,至現在的10g、11g中的undo auto tuning,使得ORA-01555的錯誤越來越少。但是這個錯誤,仍然不可避免。

Oracle undo 管理 http://www.linuxidc.com/Linux/2011-09/42706.htm

1. 出現ORA-01555錯誤,通常有2種情況:

 

(1)SQL語句執行時間太長,或者UNDO表空間過小,或者事務量過大,或者過於頻繁的提交,導致執行SQL過程中進行一致性讀時,SQL執行後修改的前鏡像(即UNDO數據)在UNDO表空間中已經被覆蓋,不能構造一致性讀塊(CR blocks)。 這種情況最多。

(2)SQL語句執行過程中,訪問到的塊,在進行延遲塊清除時,不能確定該塊的事務提交時間與SQL執行開始時間的先後次序。 這種情況很少。

 

2. 第1種情況解決的辦法:

(1)增加UNDO表空間大小

(2)增加undo_retention 時間,默認只有15分鐘

(3)優化出錯的SQL,減少查詢的時間,首選方法

(4)避免頻繁的提交

有關Undo 的更多信息,參考我的文章:

Oracle undo 表空間管理 http://www.linuxidc.com/Linux/2011-07/38006.htm

3. 第二種情況描述

 

在塊清除過程中,如果一個塊已被修改,下一個會話訪問這個塊時,可能必須查看最後一個修改這個塊的事務是否還是活動的。一旦確定該事務不再活動,就會完成塊清除,這樣另一個會話訪問這個塊時就不必再歷經同樣的過程。

要完成塊清除,Oracle 會從塊首部確定前一個事務所用的undo 段(ITL),然後確定從undo 首部能不能看出這個塊是否已經提交。

 

可以用以下兩種方式完成這種確認:

一種方式是Oracle 可以確定這個事務很久以前就已經提交,它在undo 段事務表中的事務槽已經被覆蓋。

另一種情況是COMMIT SCN 還在undo 段的事務表中,這說明事務只是稍早前剛提交,其事務槽尚未被覆蓋。

 

當滿足以下條件時,就會從defered clean 收到ORA-01555的錯誤:

首先做了一個修改並COMMIT,塊沒有自動清理(即沒有自動完成“提交清除”,例如修改了太多的塊,在SGA 塊緩沖區緩存的10%中放不下)。

其他會話沒有接觸這些塊,而且在我們這個“倒霉”的查詢(稍後顯示)命中這些塊之前,任何會話都不會接觸它們。

開始一個長時間運行的查詢。這個查詢最後會讀其中的一些塊。這個查詢從SCN t1 開始,這就是讀一致SCN,必須將數據回滾到這一點來得到讀一致性。

 

開始查詢時,上述修改事務的事務條目還在undo 段的事務表中。查詢期間,系統中執行了多個提交。執行事務沒有接觸執行已修改的塊(如果確實接觸到,也就不存在問題了)。

 

由於出現了大量的COMMIT,undo 段中的事務表要回繞並重用事務槽(ITL)。最重要的是,將循環地重用原來修改事務的事務條目。另外,系統重用了undo 段的區段,以避免對undo 段首部塊本身的一致讀。

此外,由於提交太多,undo 段中記錄的最低SCN 現在超過了t1(高於查詢的讀一致SCN)。如果查詢到達某個塊,而這個塊在查詢開始之前已經修改並提交,就會遇到麻煩。正常情況下,會回到塊所指的undo 段,找到修改了這個塊的事務的狀態(換句話說,它會找到事務的COMMIT SCN)。

如果這個COMMIT SCN 小於t1,查詢就可以使用這個塊。如果該事務的COMMIT SCN 大於t1,查詢就必須回滾這個塊。不過,問題是,在這種特殊的情況下,查詢無法確定塊的COMMIT SCN 是大於還是小於t1。相應地,不清楚查詢能否使用這個塊映像。這就導致了ORA-01555 錯誤。

 

大批量的UPDATE 或INSERT 會導致塊清除(block cleanout),所以在大批量UPDATE 或大量加載之後使用DBMS_STATS收集相關對象的統計信息,加載之後完成這些對象的清理。

 

關於塊清除這塊,在 一書中有更詳細的說明。

 

 

老熊blog上關於defered Clean的場景說明:

(1)有事務大量修改了A表的數據,或者A表的數據雖然被事務少量修改,但是一部分修改過的塊已經刷出內存並寫到了磁盤上。隨即事務提交,提交時刻為SCN1。而提交時有數據塊上的事務沒有被清除。

(2)在SCN2時刻,開始執行SELECT查詢A表,對A表進行全表掃描,而且A表很大。也可能是其他情況,比如是小表,但是是一個游標方式的處理過程,而處理過程中又非常耗時。注意,這裡SCN2與SCN1之間可能相隔了很遠,從時間上來說,甚至可能有數十天。不管怎麼樣,這在SCN1至SCN2時間之間,系統中存在大量的事務,使得UNDO表空間的塊以及UNDO段頭的事務表全部被重用過。

(3)SELECT語句在讀A表的一個塊時,發現表上有活動事務,這是由於之前的事務沒有清除所致。ORACLE根據數據塊中ITL的XID檢查事務表,這時會有2種情況:

(A)XID對應的事務表中的記錄仍然存在並發現事務已經提交,可以得到事務准確的提交SCN(commit scn),稱為SCN3,等於SCN1。很顯然,由於查詢的時刻SCN2晚於事務提交的時刻SCN1,那麼不需要構造一致性讀塊。

(B)XID對應的事務表中的記錄已經被重用,這個時候仍然表明表明事務已經被提交。那麼這個時候,Oracle沒辦法准確地知道事務的提交時間,只能記錄為這樣一個事實,事務提交的SCN小於其UNDO段的事務表中最近一次重用的事務記錄的SCN(即這個事務表最老的事務SCN)。這裡稱這個SCN為SCN4。

(4)SCN4可能遠小於SCN2,那是因為事務很早之前就已經提交。也可能SCN4大於SCN2,這是因為SELECT語句執行時間很長,同時又有大量的事務已經將事務表重用。對於後者,很顯然,Oracle會認為該事務的提交時間可能在SELECT開始執行之後。這裡為什麼說可能,是因為ORACLE只能判斷出事務是在SCN4之前提交的,並不是就剛好在SCN4提交。而此時,利用UNDO BLOCK進行一致性讀數據的構造也很可能失敗,因為UNDO BLOCK很可能已經被覆蓋,特別是SCN1遠小於SCN2的情況下。

在這種情況下,ORA-01555錯誤就會出現。

 

對這種由於表上存在未清除的事務,同時導出時間過長,UNDO段頭的事務表被全部重用,ORACLE在查詢到有未清除事務的塊時不能確定事務提交時間是否早於導出(查詢)開始時間,報ORA-01555錯誤。

老熊blog上有2個解決方法,一是提高SQL 性能,另一個是清除表上的事務,即延時塊清楚(Defered Clean)。 這個方法也很簡單,就是select。

SQL>SELECT /*+ FULL(A) */ COUNT(*) FROM BIG_TABLE A;

 

SELECT COUNT(*),速度顯然大大高於SELECT *,所需的時間也更短,出現ORA-01555錯誤的可能性就非常低了。

 

注意:

(1)使用FULL HINT,以避免查詢進行索引快速全掃描,而不是對表進行全表掃描。

(2)這裡不能為了提高性能而使用PARALLEL(並行),測試表明,在表上進行並行查詢,以DIRECT READ方式讀取表並不會清除掉表上的事務。

 

如果表過大,SELECT COUNT(*)的時間過長,那麼我們可以用下面的代碼將表分成多個段,進行分段查詢。

/* Formatted on 2011/6/29 19:18:40 (QP5 v5.163.1008.3004) */

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.relative_fno,

a.block_id,

a.blocks,

SUM (

a.blocks)

OVER (

ORDER BY

b.data_object_id,

a.relative_fno,

a.block_id)

sum2,

CEIL (

SUM (a.blocks) OVER ()

/ &trunks)

chunk_size

FROM dba_extents a, dba_objects b

WHERE a.owner = b.owner

AND a.segment_name =

b.object_name

AND NVL (a.partition_name,

'-1') =

NVL (b.subobject_name,

'-1')

AND b.data_object_id

IS NOT NULL

AND a.owner = UPPER ('&owner')

AND a.segment_name =

UPPER ('&table_name'))))

GROUP BY chunk_no

ORDER BY chunk_no) a);

 

該SQL 在執行時需要輸入幾個參數:

trunks: 表示把表分成的段數

owner: 表的所有者

table_name: 表名

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved