2011-04-25 37 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

相关问题