Возникшее исключение ORA-01555 - PullRequest
2 голосов
/ 25 апреля 2011

Мне дали решить проблему, в которой в Master Db есть таблица Scenarios, которая содержит детали всего табличного пространства, для которого мне нужно найти размер.O / P должен содержать размер таблицы (фактически использованный) и размер индекса, а не количество строк.

Итак, я написал сценарий определения размера (PL / SQL), чтобы найти размер всего табличного пространства для этого конкретногоСервер БД.

Но я получаю это исключение после нескольких дней его работы.

ORA-01555: снимок слишком старый: сегмент отката номер 9 с именем "_SYSSMU9 $" слишком мал

Я не уверен, что может быть причиной этого, поскольку размер данных не так уж велик.

Я прилагаю скрипт

    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)

Просто указать, что у пользователя есть разрешения на все БД

И тогда я суммирую всеf, чтобы найти общий размер данных в таблице.

Может кто-нибудь сказать мне, что я делаю неправильно или Что я должен сделать, чтобы исправить ошибку?

Спасибо.

Ответы [ 4 ]

8 голосов
/ 25 апреля 2011

"Может кто-нибудь сказать мне, что я делаю неправильно"

С чего начать?

ORA-01555

Это происходит в длительных запросах.Политика согласованности чтения Oracle гарантирует, что последняя запись в наборе результатов будет согласована с первой записью набора результатов.Другими словами, наш запрос не будет возвращать изменения, сделанные в каком-то другом сеансе, которые были зафиксированы после того, как мы выполнили наш запрос.Oracle делает это, подставляя записи из табличного пространства UNDO для любых измененных записей.Когда он не может этого сделать, он выбрасывает исключение SNAPSHOT TOO OLD.Это означает, что у Oracle больше нет старых версий записей, необходимых для обеспечения согласованного чтения.

Распространенная причина, по которой у Oracle больше нет данных, заключается в том, что наш длительный запрос представляет собой цикл курсора PL / SQL, который выдает операторы COMMIT.Как вы должны знать, COMMIT означает конец транзакции, и это освобождает любые блокировки, которые Oracle сохранил для нашего сеанса.Это, очевидно, включает в себя наш интерес к сессиям в табличном пространстве UNDO;Затем Oracle может свободно перезаписывать блоки UNDO, которые содержат данные, необходимые для согласованности чтения.

В вашем случае операторы COMMIT являются неявными, которые заключают в себе любой оператор DDL - включая ANALYZE.Это может не иметь значения, но кажется, что кто-то обновляет таблицу SCENARIOS, пока ваша программа работает, вероятный случай чего-то, что занимает несколько дней.

Использование ANALYZE

Этоэто плохо по нескольким причинам.Во-первых, это давно устарело: вы используете 10g, вам нужно использовать DBMS_STATS для сбора статистики .Но подождите, это еще не все.

Сбор статистики - это не то, что следует делать слишком часто.В большинстве систем статистика достигает плато стабильности, на котором они достаточно точны, даже когда им несколько месяцев.Поэтому частый сбор статистики в лучшем случае является пустой тратой времени.Это может быть гораздо хуже: существует риск, что новая статистика порождает менее эффективный план, чем текущий.Так что на самом деле сбор статистики должен осуществляться контролируемым образом.Одним из преимуществ DBMS_STATS является то, что мы можем настроить его для отслеживания скорости изменений, применяемых к нашим таблицам, и собирать статистику только тогда, когда они достигают определенной устаревания. Узнайте больше.

Конечно, вы используете ANALYZE только для того, чтобы получить актуальное использование пространства для индексов, что подводит меня к третьему пункту:

Безумие

Вы выбираете каждую строку всех таблиц, которые вас интересуют, и суммируете фактический размер всех их столбцов, с - если я правильно понял - отдельным запросомдля каждого столбца.Это безумие.

Oracle предоставляет представления, которые показывают объем пространства, используемого данной таблицей.USER_SEGMENTS должно быть достаточно, хотя USER_EXTENTS также доступна.SEGMENT_NAME - это индекс или имя таблицы.Суммирование столбца БАЙТЫ даст вам точный размер занимаемой площади каждой таблицы.

Конечно, некоторые из этих выделенных экстентов будут пустыми, поэтому вы можете подумать, что эти цифры будут немного завышены.Но:

  1. Распределенные экстенты на самом деле представляют собой более точную картину использования пространства, поскольку она учитывает пространство, которое удерживается столом.
  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>

Небольшой стол, возможно, нереально быстрый.Вот больший, без сгустков.

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, чтобы получитьсоответствующие имена столбцов.Я думаю, что это предпочтительнее, чем повторный анализ индексов.Он не будет работать для определения размеров любых индексов TEXT, которые вы можете иметь для столбцов CLOB или BLOB.Для тех, кому нужно использовать CTX_REPORT.INDEX_SIZE () , хотя при этом создается отчет, который вам нужно будет проанализировать, чтобы получить полезные цифры (формат XML может быть полезен в этом отношении).

1 голос
/ 25 апреля 2011

Ваша процедура не оптимальна. Вы объявляете много курсоров внутри циклов. Анализируются для каждого выполнения цикла. Разумнее определить все курсоры на верхнем уровне. Вы также можете получить большую производительность, используя массовые коллекции и массовые вставки. Вы получите максимальную производительность, если сможете обновить код с PL / sql до sql. Это не всегда возможно, но попробуйте использовать минимум процедурного кода.

Вы можете попытаться увеличить срок хранения базы данных для отмены, но он уже удивительно высок, учитывая тот факт, что ваш снимок слишком старый выскочил через 9 дней.

О какой версии базы данных мы говорим?

1 голос
/ 25 апреля 2011

Вы получаете ошибку ORA-01555, потому что вы выбираете коммиты . Вы открываете курсор, и пока вы читаете с курсора, вы совершаете транзакции.

Когда вы открываете курсор, Oracle в этот момент гарантирует, какие данные вы получите. Если вы или другие пользователи затем измените данные, по которым этот курсор будет позже повторяться, Oracle вернется к отмене, чтобы получить исходные данные, которые вы увидите, если данные не были изменены. В конечном счете, ошибка ORA-01555 указывает на то, что у Oracle закончилась отмена, и он не мог идти дальше. В этом случае Oracle выдает эту ошибку, потому что ему приходится возвращаться через слишком много подтвержденных транзакций.

Ваш код явно нигде не COMMIT, но кажется, что оператор ANALYZE, как и все Oracle DDL, выполняет неявную фиксацию до и после выполнения. (Оператор TRUNCATE в верхней части также выполняет неявную фиксацию до и после, но это не проблема, поскольку вы вызываете ее только один раз.)

Вместо этого я бы сделал следующее:

  • Извлекает имена и имена владельцев / табличных пространств всех индексов для анализа и собирает их в PL / SQL вложенную таблицу . (Кажется, что имена ваших табличных пространств совпадают с именами владельцев схем - это правильно?)
  • Перебрать эту вложенную таблицу и вызвать ANALYZE INDEX index_name VALIDATE STRUCTURE для каждой.
  • Затем запустите оставшуюся часть кода без операторов ANALYZE INDEX ..., так как все остальное - просто запрос данных.
0 голосов
/ 25 апреля 2011

Oracle использует управление несколькими версиями параллелизма, т. Е. Хранит старые версии записей для согласованных результатов в длительных запросах и до тех пор, пока новая версия не зафиксирована.

Если для долго выполняющегося запроса потребуется старая версия, но в то же время эта версия была удалена, вы получаете ошибку «Снимок слишком старый».

Лучший способ избежать этой ошибки - ускорить ваш запрос. Еще один способ - увеличить размер журнала отката.

Но поскольку ваша программа, очевидно, работает в течение нескольких дней, вам действительно нужно сделать ее быстрее, т.е. намного быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...