Индексы Oracle «ломаются» - PullRequest
1 голос
/ 20 декабря 2011

Я работаю над проектом хранилища данных, и поэтому я реализую некоторые ETL функции в пакетах.Сначала я столкнулся с проблемой на моем развивающемся ноутбуке и подумал, что это как-то связано с моей установкой Oracle, но теперь он «распространился» на рабочие серверы.Две функции «иногда» становятся невероятно медленными.Мы внедрили систему ведения журналов, предоставляя нам вывод в таблицу журналирования каждые х строк.Когда функции обычно требуется около 10 секунд на фрагмент, «иногда» функциям требуется до 3 минут.После перестройки некоторых индексов и перезапуска функции она снова выполняется так же быстро, как и раньше.К сожалению, я не могу сказать, какой именно это индекс, поскольку перезапуск функции и создание курсора, который она использует для своей работы, занимает некоторое время, и у нас нет времени, чтобы проверить каждый индекс самостоятельно, поэтому я просто перестроил всеиндексы, которые потенциально используются функцией, и перезапустите ее.

Функции, в которых возникла проблема, используют курсор для выбора данных из таблицы с приблизительно 50–200 миллионами записей, соединенных небольшой таблицей с приблизительно 50-500 записей.Условие соединения - это сравнение строк.Затем мы используем первичный ключ из маленькой таблицы, которую мы получаем из объединения, чтобы обновить внешний ключ в главной таблице.Процесс обновления выполняется с помощью повторяющегося цикла, что, как оказалось, позволяет сэкономить много времени.

Вот упрощенная версия структуры таблиц обеих таблиц:

CREATE TABLE "maintable" 
(   "pkmid" NUMBER(11,0) NOT NULL ENABLE, 
"fkid" NUMBER(11,0), 
"fkstring" NVARCHAR2(4) NOT NULL ENABLE, 
 CONSTRAINT "PK_MAINTABLE" PRIMARY KEY ("pkmid");

CREATE TABLE "smalltable" 
(   "pksid" NUMBER(11,0) NOT NULL ENABLE, 
"pkstring" NVARCHAR2(4) NOT NULL ENABLE, 
 CONSTRAINT "PK_SMALLTABLE" PRIMARY KEY ("pksid");

Обе таблицы имеютиндексы на их строковых столбцах.Добавляя первичные ключи, я каждый раз перестраиваю 4 индекса каждый раз, когда возникает проблема.

Мы получаем наши данные таким образом, что у нас есть только fkstring в основной таблице, а fkid имеет значение null.На первом этапе мы заполняем небольшой стол.Это занимает всего несколько минут и выполняется следующим образом:

INSERT INTO smalltable (pksid, pkstring)
    SELECT SEQ_SMALLTABLE.NEXTVAL, fkstring
        FROM 
        (
            SELECT DISTINCT mt.fkstring
                FROM maintable mt                        
            MINUS
            SELECT st.pkstring
                FROM smalltable st
        );
        commit;

Эта функция никогда не вызывает проблем.

Следующая функция работает (это упрощенная версия функции - я удалилаведение журнала и обработка исключений и переименование некоторых переменных):

function f_set_fkid return varchar2 is

    cursor lCursor_MAINTABLE is
        SELECT MT.PKmID, st.pksid
            FROM maintable mt
            JOIN smalltable st ON (mt.fkstring = st.pkstring)
            WHERE mt.fkid IS NULL;
    lIndex number := 0;
    lExitLoop boolean := false;

    type lCursorType is table of lCursor_MAINTABLE%rowtype index by pls_integer;
    lCurrentRow lCursor_MAINTABLE%rowtype;
    lTempDataArray lCursorType;
    lCommitEvery constant number := 1000;

    begin

        open lCursor_MAINTABLE;
            loop

                -- get next row, set exit condition
                fetch lCursor_MAINTABLE into lCurrentRow;
                if (lCursor_MAINTABLE%notfound) then
                    lExitLoop := true;
                end if;

                -- in case of cache being full, flush cache
                if ((lTempDataArray.count > 0) AND (lIndex >= lCommitEvery OR lExitLoop)) then
                    forall lIndex2 in lTempDataArray.FIRST..lTempDataArray.LAST
                        UPDATE maintable mt 
                            set fkid = lTempDataArray(lIndex2).pksid
                            WHERE mt.pkmid = lTempDataArray(lIndex2).pkmid;     
                    commit;
                    lTempDataArray.delete;
                    lIndex := 0;                           
                end if;                                                 

                -- data handling, fill cache
                if (lExitLoop = false) then
                    lIndex := lIndex + 1;                                 
                    lTempDataArray(lIndex). := lCurrentRow;
                end if;

                exit when lExitLoop;

            end loop;

        close lCursor_MAINTABLE; 

        return null;

    end;  

Я был бы очень благодарен за любую помощь.

PS Я знаю, что массовый сбор в ускорит функцию и, вероятно, такженемного упростить код, но в данный момент мы довольны скоростью функции, которая обычно есть.Изменение функции для использования группового сбора входит в наш план на следующий год, но на данный момент это не вариант (и я сомневаюсь, что это решит эту проблему индекса).

Ответы [ 2 ]

2 голосов
/ 20 декабря 2011

Если у вас есть таблица, в которой число строк сильно колеблется (как при загрузке ETL), я бы использовал статистику полностью загруженной таблицы на протяжении всего процесса загрузки.

Итак, генерируйте статистику, когда ваша таблица полностью загружена, а затем используйте эту статистику для последующих загрузок.

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

В вашем случае статистика для столбцов fkstring и fkid очень важна, так как эти два столбца активно участвуют в процедуре, имеющей проблемы с производительностью.

0 голосов
/ 20 декабря 2011
function f_set_fkid return varchar2 is 

        cursor lCursor_MAINTABLE is 
            SELECT MT.PKmID, st.pksid 
                FROM maintable mt 
                JOIN smalltable st ON (mt.fkstring = st.pkstring) 
                WHERE mt.fkid IS NULL; 
        commit_every INTGER := 1000000;
        commit_counter INTEGER :=0;     
begin 
   for c in lCursor_MAINTABLE 
   loop
      UPDATE maintable mt  
      set fkid = c.pksid 
      WHERE mt.pkmid = c.pkmid;      
      commit_counter := commit_counter+1;
      if mod(commit_every,commit_counter) = 0
      then
         commit;
         commit_counter := 0;
      end if;
   end loop; 
   return null; 
end;   
...