2011-04-25 34 views
2

我得到了一個需要解決的問題,其中在主Db中有一個名爲Scenarios的表,其中包含所有需要查找大小的表空間的詳細信息。 O/P應該包含表大小(實際消耗)和索引大小以及行數。因此,我編寫了一個調整腳本(PL/SQL)來查找特定DB服務器上所有表空間的大小。遇到異常ORA-01555

但是我運行了幾天之後就會遇到這個特殊的異常。

ORA-01555:快照太舊:名稱爲「_SYSSMU9 $」過小

回滾段9號我不知道什麼可能導致這一點,因爲數據量不是很大。

我附上腳本

SET SERVEROUTPUT ON size '10000000' 
declare 
TYPE cur_typ IS REF CURSOR; 
a_Temp number := 0; 
x_Total number := 0; 
i number := 0; 
c_cursor cur_typ; 
query_str varchar2(500); 
num_long Long; 
currentScenarioDB nvarchar2(255); 
tableExists number := 0; 
scenarioId varchar2(50); 
scenarioName varchar2(100); 
dbIdentifier nvarchar2(50); 
queryToFindScenarioNameAndId varchar2(400) := 'select scenarioId,name from scenarios where dbidentifier = '; 
selectQuery varchar2(400) := 'select scenarioId,name from scenarios where dbidentifier = '; 
insertStatement varchar2(2000) := 'Insert Into ScenarioTableAndIndexSize values (:1,:2,:3,:4,:5,:6,:7) '; 
-- scenarioId,scenarioname,,dbIdentifier,tablename,dataSize,IndexSize,rowNumber 
tableIndexSize number := 0; 
numOfRows number := 0; 
rowNum number := 0; 
tableDataSize number := 0; 
Cursor getScenarioDb is select dbidentifier from scenarios where dbidentifier IN (select Distinct(TABLESPACE_NAME) from dba_tables); 
begin 
DBMS_OUTPUT.ENABLE(10000000); 
execute immediate 'truncate table ScenarioTableAndIndexSize'; 
open getScenarioDb; 
fetch getScenarioDb into currentScenarioDB; 
while getScenarioDb%found 
loop 
queryToFindScenarioNameAndId := selectQuery || '''' || currentScenarioDB || ''''; 
execute immediate queryToFindScenarioNameAndId into scenarioId,scenarioName; 
       declare 
       queryToFindNoofRows varchar2(1000); 
     queryConstruct varchar2(32767) := ''; 
     outputTableInScenarioDb nvarchar2(256); 
     Cursor getTablesInScenario is select DISTINCT TABLE_NAME from dba_tables where owner = currentScenarioDB and TABLE_NAME not like 'BIN%' and table_name != 'SCENARIOTABLEANDINDEXSIZE' order by table_name; 
     begin 
       tableExists := 0; 
     open getTablesInScenario; 
     fetch getTablesInScenario into outputTableInScenarioDb; 
     while getTablesInScenario%found 
     loop 
       queryConstruct := 'select nvl(sum ('; 
       tableIndexSize := 0; 
       tableDataSize := 0; 
       numOfRows := 0; 
       queryToFindNoofRows := 'select count(*) from '|| currentScenarioDB || '.' ||outputTableInScenarioDb; 
       execute immediate queryToFindNoofRows into numOfRows; 
       if numOfRows > 0 then 
---------------------------Beginning Of Section to find Table data Size------------------------------------------------------------------------------------------------ 
        declare 
         Cursor getColumnsInTables is select * from dba_tab_columns where Table_Name = outputTableInScenarioDb and owner = currentScenarioDB; 
         dbaTabColumnRow dba_tab_columns%rowtype; 
         dataType varchar2(40); 
         fields varchar2(1000); 
         begin 
         open getColumnsInTables; 
         fetch getColumnsInTables Into dbaTabColumnRow; 
         while getColumnsInTables%found 
         loop 
         dataType := dbaTabColumnRow.DATA_TYPE; 
        if dataType = 'CLOB' then 
         fields := 'nvl(DBMS_LOB.GETLENGTH(' || dbaTabColumnRow.COLUMN_NAME ||'),0)'; 
        elsif dataType = 'BLOB' then 
         fields := 'nvl(DBMS_LOB.GETLENGTH('|| dbaTabColumnRow.COLUMN_NAME ||'),0)'; 
        elsif dataType = 'LONG' then 
         fields := 'nvl(VSIZE(''''),0)'; 
         x_Total := 0; 
         query_str := 'SELECT ' || dbaTabColumnRow.COLUMN_NAME || ' FROM ' || currentScenarioDB || '.' ||outputTableInScenarioDb; 
             OPEN c_cursor FOR query_str; 
            LOOP 
            FETCH c_cursor INTO num_long; 
            EXIT WHEN c_cursor%NOTFOUND; 
          a_Temp:=length(num_long); 
          x_Total:= x_Total + a_Temp; 
            END LOOP; 
          CLOSE c_cursor; 
        else 
         fields := 'nvl(vsize(' || dbaTabColumnRow.COLUMN_NAME || '),0)'; 
        end if; 
          fetch getColumnsInTables Into dbaTabColumnRow; 
         if getColumnsInTables%found then 
         queryConstruct := queryConstruct || fields||'+'; 
        else 
        queryConstruct := queryConstruct || fields; 
        end if; 
         end loop; 
         end; 
             queryConstruct := queryConstruct || '),0) as sizeOfTable from ' || currentScenarioDB || '.' ||outputTableInScenarioDb;    
             --dbms_output.put_line(queryConstruct); 
             execute immediate queryConstruct into tableDataSize; 
---------------------------End Of Section to find Table data Size------------------------------------------------------------------------------------------------------------- 
        ---------------Section To find index size 
         declare 
      Index_Name nvarchar2(4000); 
      sql_statement varchar2(1000) := 'select nvl(USED_SPACE,0) from index_stats'; 
      stat1 varchar2(1000) := 'analyze index '; 
      stat2 varchar2(1000) := ' validate structure'; 
      stat3 varchar2(2000) := ''; 
      size1 number := 0; 
      cursor indexOnTable is select INDEX_NAME from dba_indexes where tablespace_name = currentScenarioDB and table_name = outputTableInScenarioDb and index_type = 'NORMAL'; 
       begin 
       open indexOnTable; 
       fetch indexOnTable into Index_Name; 
       while indexOnTable%found 
       loop 
        stat3 := stat1 || currentScenarioDB ||'.' ||Index_Name || stat2; 
        execute immediate stat3; 
        execute immediate sql_statement into size1; 
        tableIndexSize := tableIndexSize + size1; 
       fetch indexOnTable into Index_Name; 
       end loop; 
       close indexOnTable; 
     end; 
        -----end of section to find index size 
      else 
      rowNum := rowNum + 1; 
      end if; 
         tableDataSize := x_Total + tableDataSize; 
      execute immediate insertStatement using scenarioId,scenarioName,currentScenarioDB,outputTableInScenarioDb,tableDataSize,tableIndexSize,numOfRows; 
          x_Total := 0; 
      fetch getTablesInScenario into outputTableInScenarioDb; 
    end loop; 
    end; 
    fetch getScenarioDb into currentScenarioDB; 
    end loop; 
    close getScenarioDb; 
end; 

表的大小是發現了這種方式:

  1. 列表項 如果該字段類型的LOB然後計算出它的大小我用nvl(DBMS_LOB.GETLENGTH(),0)
  2. 如果該字段的類型是Long,那麼我將遍歷所有的Long值並使用內置的Length()函數查找它們的大小
  3. 如果該字段是任何其他類型的我用NVL的(VSIZE(),0)

只需指定用戶有權限的所有的DB

然後我總結他們都往上找表中的總數據大小。

有人可以告訴我我做錯了什麼,或者我應該怎麼做來修復錯誤?

謝謝。

+1

我建議你創建一個新的問題,詢問如何有效地解決手頭的問題。運行一個需要幾天時間才能完成的PL/SQL程序將會導致頭痛無期。一旦你解決了這個問題,你將不得不再次運行好幾天纔會遇到下一個問題。 – Codo 2011-04-25 09:16:54

+0

95%的時間,長時間運行過程中的快照太舊是由於跨提交選擇/提取。我沒有深入上面的每一個細節,但我敢打賭,你有一些驅動光標從它下面正在更新的表中選擇。爲了保持讀取的一致性,Oracle需要引用回滾段來繼續從表中選擇您(或某個進程)不斷修改的數據,因此需要越來越多的回滾 – tbone 2011-04-25 12:09:04

+0

交叉發佈[DBA StackExchange](http:// dba.stackexchange.com/questions/2358/encountering-exception-ora-01555) – Sathya 2011-04-25 16:35:58

回答

0

Oracle使用多版本併發控制,即在長時間運行的查詢中保留舊版本的記錄以獲得一致的結果,並且只要未提交新版本即可。

如果長時間運行的查詢需要舊版本,但此版本同時被丟棄,則會出現「快照太舊」的錯誤。

避免此錯誤的最佳方法是加快查詢速度。另一個是增加UNDO日誌的大小。

但是由於你的程序明顯運行了好幾天,你真的需要讓你的程序更快,即更快。

1

您的程序不是最佳的。你在循環內部聲明瞭很多遊標。對每個循環的執行都進行解析。更聰明的是在頂層定義光標。您也可以通過使用批量收集和批量插入來獲得很多性能。如果您可以將代碼從PL/sql升級到sql,那麼您將獲得最佳性能。 這可能不總是可能的,但嘗試使用程序代碼的最小值。

您可以嘗試增加數據庫的撤消保留時間,但考慮到您的快照在9天后彈出太早,它已經非常高。

我們在談論什麼版本的數據庫?

+0

快照過時並未在9天后彈出。它會在幾天後彈出。撤消保留值爲12 HRS – Egalitarian 2011-04-25 09:15:54

+0

另外,除了使用if-else – Egalitarian 2011-04-25 09:32:32

+0

@平均值(如果保留時間爲12小時)和1555之外,如何在遊標中獲得特定結果行集合9天后出現,你真幸運,撤消表空間非常大,或者系統的其他部分非常安靜。你不會總是能夠離開程序代碼,在大多數情況下,你可以通過tweeking你的SQL,智能where子句等。你有什麼版本的rdbms?你能跟蹤會話並通過tkprof運行嗎?你會在第二秒找到瓶頸。 (我沒有研究你的代碼)。我可以更聰明地進行一些模式複雜的查詢,最終運行速度更快,因爲它可以一次性獲取大部分數據。 – 2011-04-25 09:54:53

1

你會得到ORA-01555錯誤,因爲你是提交提交。你打開一個遊標,當你從光標讀取時,你提交事務。

當您打開遊標時,Oracle會保證您將獲得的數據是什麼。如果您或其他用戶隨後更改了該遊標稍後會迭代的數據,則Oracle將返回到撤消狀態以獲取您將看到的原始數據,如果數據未被更改。最終,ORA-01555錯誤表示Oracle耗盡了撤消,並且無法再往前走。在這種情況下,Oracle正在拋出這個錯誤,因爲它必須通過太多的已提交的事務返回。

你的代碼在任何地方都沒有明確地指出COMMIT,但似乎ANALYZE語句像所有Oracle DDL一樣,在執行前後執行隱式提交。 (頂部的TRUNCATE聲明還沒有一個隱含的前後提交,但是這不是一個問題,因爲你只有一次調用它。)

我會做什麼,而不是將:

  • 抓取所有索引的名稱和所有者/表空間名稱進行分析並將它們批量收集到PL/SQL nested table中。 (看起來您的表空間名稱與您的架構所有者名稱相同 - 是否正確?)
  • 循環遍歷此嵌套表併爲每個表調用ANALYZE INDEX index_name VALIDATE STRUCTURE
  • 然後運行其餘的代碼,沒有ANALYZE INDEX ...語句,因爲其他所有內容都只是查詢數據。
+0

是的,表空間名稱與您的模式所有者名稱相同 – Egalitarian 2011-04-25 10:01:14

+0

@Egalitarian:當然,表空間名稱與* your *模式所有者名稱相同,而不是* mine *? – 2011-04-25 10:31:17

+0

哎呀,讓你爲 – Egalitarian 2011-04-25 10:36:56

8

「能有人告訴我,我在做什麼錯 」

從哪裏開始?

ORA-01555

這發生在長時間運行的查詢。 Oracle的讀取一致性策略確保結果集中的最後一條記錄與結果集的第一條記錄保持一致。換句話說,我們的查詢不會返回在我們發出查詢後提交的其他會話中所做的更改。 Oracle通過用UNDO表空間中的記錄替換任何已更改的記錄來完成此操作。當它不能這樣做時,它會拋出SNAPSHOT TOO OLD異常。這意味着Oracle不再需要提供一個讀一致視圖的舊版本記錄。

Oracle不再擁有數據的一個常見原因是因爲我們長時間運行的查詢是一個PL/SQL遊標循環,它發出COMMIT語句。正如您應該知道的那樣,COMMIT表示事務結束,並釋放Oracle爲我們的會話保留的任何鎖。這顯然包括我們會議對UNDO表格空間的興趣; Oracle隨後可以自由覆蓋包含需要讀取一致性的數據的UNDO塊。

在你的情況下,COMMIT語句是包含任何DDL語句(包括ANALYZE)的隱式語句。這可能並不重要,但似乎有人在您的程序運行時更新了SCENARIOS表,這可能會導致需要幾天的時間。

使用ANALYZE

這是不好的原因有幾個。首先,它已被棄用了一段時間:你在10克,你應該使用DBMS_STATS to gather statistics。但是,等等,還有更多。

收集統計數據不應該過於頻繁。在大多數系統中,統計數據實現了穩定的高原,即使在幾個月大的時候它們也足夠準確。所以,頻繁收集統計資料最好是浪費週期。情況可能會更糟糕:存在新的統計數據產生比現有計劃效率低的計劃的風險。所以實際上統計數據收集應該以受控的方式完成。 DBMS_STATS的優勢之一是我們可以對其進行配置,以監視應用於表的變更的速率,並且只在統計數據達到一定的陳舊時重新收集統計信息。 Find out more.

當然你只使用分析得到的索引上最新的空間使用,這bings我的第三點:

瘋狂

您是選擇每所有你感興趣的表中的行,並且總計它們所有列的實際大小,如果我沒有正確理解,就爲每一列單獨查詢。瘋了吧。

Oracle提供的視圖顯示了給定表使用的空間量。雖然USER_EXTENTS也可用,但USER_SEGMENTS應該足夠了。 SEGMENT_NAME是索引或表名。總結BYTES列會給你一個確切的大小的每個表的足跡。

當然,其中一些分配的範圍將爲空,因此您可能認爲這些數字會略高估。但是:

  1. 的alloocated程度實際上是空間使用的更準確圖片,因爲它允許這是由表中保存的空間。
  2. 任何感知到的「準確性」損失將在幾秒鐘內而不是幾天的查詢中得到償還。
  3. 接下來,查詢將返回現在的位置,而不是三天內空間使用情況的變化圖,因此這些數字更有用。

「但背後 整個動機寫這整個PL/SQL腳本是 得到實際未分配的空間」

好吧,讓我們來解決這一點。你的腳本的主要問題是它解決事物RBAR;事實上,比這更糟,RBARBAC。所以你發出一個查詢矩陣,每個表中的每一行對應一個查詢矩陣。 SQL是基於集合的語言,如果我們這樣對待它,它會好得多。

此過程組裝一個動態查詢,該查詢組合一個SELECT以獲取給定表的總大小和記錄數。

create or replace procedure get_table_size 
    (p_tabn in user_tables.table_name%type 
     , p_num_rows out pls_integer 
     , p_tot_size out pls_integer) 
is 
    stmt varchar2(32767) := 'select count(*), sum('; 
    n_rows pls_integer; 
    n_size pls_integer; 
begin 
    for r in (select column_name, data_type, column_id 
       from user_tab_columns 
       where table_name = p_tabn 
       order by column_id) 
    loop 
     if r.column_id != 1 
     then 
      stmt := stmt ||'+'; 
     end if; 
     stmt := stmt || 'nvl('; 
     if r.data_type in ('CLOB', 'BLOB', 'BFILE') 
     then 
      stmt := stmt || ' dbms_lob.getlength('||r.column_name||')'; 
     else 
      stmt := stmt || ' vsize('||r.column_name||')'; 
     end if; 
     stmt := stmt || 'nvl)'; 
    end loop; 
    stmt := stmt || ') from '||p_tabn; 
    execute immediate stmt into n_rows, n_size; 
    p_num_rows := n_rows; 
    p_tot_size := n_size; 
end; 
/

它不包括塊頭開銷(每行3個字節),但這是一個簡單的算術問題。

這是在行動:

SQL> desc t34 
Name          Null? Type 
----------------------------------------- -------- ---------------------------- 
SEQ_NUM           NUMBER 
UNIQUE_ID           NUMBER 
NAME            VARCHAR2(20 CHAR) 
LONG_COL           CLOB 

SQL> 
SQL> set timing on 
SQL> var n1 number 
SQL> var n2 number 
SQL> exec get_table_size('T34', p_num_rows=>:n1, p_tot_size=>:n2) 

PL/SQL procedure successfully completed. 

Elapsed: 00:00:00.89 
SQL> print n1 

     N1 
---------- 
     11 

SQL> print n2 

     N2 
---------- 
    135416 

SQL> 

小桌子,或許是不切實際的快。這是一個更大的,沒有clobs。

SQL> exec get_table_size('BIG_TABLE', p_num_rows=>:n1, p_tot_size=>:n2) 

PL/SQL procedure successfully completed. 

Elapsed: 00:00:10.65 
SQL> print n1 

     N1 
---------- 
    4680640 

SQL> print n2 

     N2 
---------- 
189919606 

SQL> 

流逝的時間還是不錯,嗯?

關於索引的空間,類似的查詢會的工作,從USER_IND_COLUMNS只有駕駛才能獲得相應的列名。我認爲這比重新分析索引更可取。它不適用於調整您在CLOB或BLOB列上可能具有的任何TEXT索引。對於那些需要使用CTX_REPORT.INDEX_SIZE()的人來說,雖然這會產生一個需要在oder中解析以獲得有用數字的報告(XML格式在這方面可能會有所幫助)。

+0

你說得對,我應該使用USER_SEGMENTS獲得分配的空間,但背後寫這整個PL/SQL腳本整個動機是爲了獲得實際的未分配空間,所以腳本。 – Egalitarian 2011-04-28 04:45:45

+0

@平等 - 在大多數情況下,分配的空間「足夠好」。我對你爲什麼需要實際感興趣。無論如何,我已經編輯了我的答案,並提出了一個更有效的方法。 – APC 2011-04-30 03:50:37

+0

@Egalitarian:這聽起來像一個不可能完成的任務。壓縮,加密,尾隨空列,虛擬列等會發生什麼? – 2011-04-30 04:42:34

相關問題