Преобразование данных Oracle - Bulk/ForALL не будет работать

Я пытаюсь найти лучший способ перетаскивания данных из одной таблицы в другую в рамках более крупного проекта обработки. Я думал, что смогу сделать это с помощью BULK COLLECT и FORALL и набрать значительную скорость, но я не думаю, что смогу обрабатывать ссылки на отдельные столбцы с помощью BULK COLLECT...

У меня есть проект миграции данных/приложений (MSSQL в Oracle 11.2) по наследству. Я пытаюсь оптимизировать и проверять от начала до конца... Первым шагом процесса является импорт устаревших данных (таблица базы данных, 4,5 млн записей, 170 столбцов, все в строковом формате) в другую таблицу.

Первоначальное преобразование было основано на курсоре, цикл за строкой, при этом каждый столбец проходил по крайней мере одну функцию для очистки/преобразования. Это сработало, но в тестовой системе потребовалось слишком много времени — более 12 часов, чтобы преобразовать 4,5 миллиона записей из одной таблицы в другую с помощью очень простых функций. В локальной реализации, к которой у меня есть доступ, они оказались ограничены 13000 номеров идентификаторов единиц более 220 тыс. записей.

Я настроил еще более ограниченную систему разработки на своем ноутбуке для тестирования альтернативных методов — и могу увеличить скорость импорта более чем в 5 раз, но это все еще курсор / строка за строкой. Я установил для таблицы NOLOGGING и использую подсказку APPEND. Я тестировал с/без индексов. Я не могу выполнить SELECT INTO с этой таблицей размеров — она просто задыхается.

Есть ли другая / лучшая техника? Как еще можно поднять скорость конвертации? Я делаю это неправильно с BULK COLLECT (т.е. есть ли способ ссылаться на отдельные поля?)

Если у кого есть инфа, отпишитесь! Я включаю очень урезанную версию процедуры, поэтому я могу показать свою попытку использования. То же самое (почти) выполняется как обычный цикл курсора, только без индексов FORALL и (i). Я получаю сообщение об ошибке ORA-00913: Too Many Values. Я рассмотрел полный оператор вставки, сопоставляя поля со значениями. Я проверил функции преобразования данных - они работают для обычных столбцов в качестве параметров. Мне интересно, не работают ли они с BULK COLLECT и/или FORALL из-за индексов??

ОБНОВЛЕННАЯ ИНФОРМАЦИЯ: это в системе с ограниченным доступом, и до сих пор (ожидание учетных записей) мне приходилось удаленно диагностировать «настоящую» (клиентскую) систему DEV, работая с локальная система — код профилирования, данные, время и т. д. Мои рекомендации были внесены другим разработчиком, который сообщал мне результаты. Шутки в сторону. Однако... @Mark, @Justin - Обычно я бы избавлялся от любых курсоров, а не?Абсолютно? необходимо, и используйте SELECT INTO, где это возможно. Обычно это моя первая рекомендация по старому коду PL/SQL... ("Почему. Так. Курсор?" в гриме Джокера). Это первое, что я попробовал в локальной системе, но это просто замедлило работу сервера, и я прекратил тестирование. Это было до того, как был реализован сокращенный NOLOGGING — это то, что я попытаюсь сделать, когда смогу прикоснуться к системе разработки.
Посмотрев на время, запросы, соединения, индексы и поплакав, я рекомендовал NOLOGGING и преобразование в INSERT /* + APPEND */ -- что позволило выиграть время другим процессам, в основном таблицам, созданным на основе объединений.

re: "OID ‹= '000052000'" - когда они установили там первый преобразованный код в системе cust dev, им пришлось ограничить количество записей, которые они преобразовали из таблицы PMS_OHF. Первоначально они могли получить 13000 идентификаторов персонала для обработки за разумное время. Эти 13 000 идентификаторов будут составлять около 220 000 записей, так что это то, что они перемещали, когда я пришел на борт. Некоторые перезаписи, исправления соединения и добавление NOLOGGING/Insert Append имели достаточно большое значение, чтобы они продолжались. В локальной системе я подумал, что 13000 слишком мало — я не думаю, что получу осмысленное сравнение с устаревшим результатом — поэтому я увеличил его и увеличил. Я должен быть смелым и попробовать полное преобразование в системе разработки ноутбука - здесь я могу, по крайней мере, наблюдать за тем, что происходит через EM ... правительство не позволит своим администраторам баз данных использовать его. (!?)

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ: -- после того, как снова обдумал ошибку 00913 и вспомнил о других проектах, я понял, что более ранние ошибки возникали, когда в функцию, которая ожидала один элемент, передавалось более одного элемента... что указывает мне на то, что я пытался использовать имена полей с индексами в цикле BULK COLLECT. Я пересмотрел пару презентаций Стивена Фейерштейна на YT и думаю, что наконец понял. Простые веб-примеры... Я делал свои шрифты горизонтально, а не вертикально (или наоборот)... чтобы получить вызовы функций для работы, я думаю, что мне нужно создать ТИП для каждого поля и МАССИВ/ТАБЛИЦУ этого ТИПА. Внезапно (170 раз) я думаю, что я посмотрю некоторые уроки Тома Кайта по ручному параллелизму и спрошу wx, у меня будет доступ к новому (11.2?) интерфейсу DBMS_PARALLEL_EXECUTE - в чем я сомневаюсь. Кроме того, не зная больше о системе cust dev, кроме описаний, которые лучше всего назвать «неадекватными», я не знаю, что wx //ism был бы огромной помощью. Мне нужно прочитать о //изме

Все, что я знаю, это то, что мне нужно выполнить несколько полных прогонов, иначе я не буду чувствовать себя комфортно, говоря, что наши результаты «достаточно близки» к устаревшим результатам. У нас может не быть большого выбора, кроме многодневного полного цикла тестирования.

      PROCEDURE CONVERT_FA IS    

    CURSOR L_OHF IS   -- Cursor used to get SOURCE TABLE data
        SELECT * 
        FROM TEST.PMS_OHF -- OHF is legacy data source
        where  OID <= '000052000'   -- limits OHF data to a smaller subset
        ORDER BY ID ;

    L_OHF_DATA TEST.PMS_OHF%ROWTYPE;
    L_SHDATA TEST.OPTM_SHIST%ROWTYPE;

    Type hist_Array is table of TEST.PMS_OHF%ROWTYPE;
    SHF_INPUT hist_array ; 



    Type Ohist_Array is table of TEST.OPTM_SHIST%ROWTYPE;
    TARG_SHIST ohist_Array ;

    n_limit number := 1000 ;    

  BEGIN

    begin

      OPEN L_OHF;

      LOOP 
        FETCH L_OHF BULK COLLECT INTO SHF_INPUT LIMIT n_limit ;
        FORALL i in 1 .. n_limit
          INSERT INTO TEST.OPTM_SHIST
      (  -- There are 170 columns in target table, requiring diff't xformations
              RECORD_NUMBER , UNIQUE_ID , STRENGTH_YEAR_MONTH , FY , FM , ETHNIC , 
              SOURCE_CODE_CURR , SOURCE_CODE_CURR_STAT , 
                -- ... a LOT more fields
              DESG_DT_01 ,  
                -- and some place holders for later
              SOURCE_CALC , PSID ,  GAIN_CURR_DT_CALC 
      )
      values
      ( -- examples of xformatiosn
            SHF_INPUT.ID(i) ,
            '00000000000000000000000' || SHF_INPUT.IOD(i) ,
            TEST.PMS_UTIL.STR_TO_YM_DATE( SHF_INPUT.STRYRMO(i) ) ,
            TEST.PMS_UTIL.STR_TO_YEAR( SHF_INPUT.STRYRMO(i) ) ,
            TEST.PMS_UTIL.STR_TO_MONTH( SHF_INPUT.STRYRMO(i) ) ,
            TEST.PMS_UTIL.REMOVE_NONASCII( SHF_INPUT.ETHNIC(i) ) ,
            -- ... there are a lot of columns
            TEST.PMS_UTIL.REMOVE_NONASCII( SUBSTR( SHF_INPUT.SCCURPRICL(i),1,2 ) ) ,
            TEST.PMS_UTIL.REMOVE_NONASCII( SUBSTR( SHF_INPUT.SCCURPRICL(i),3,1 ) ) ,   

            -- an example of other transformations
            ( case 
                when ( 
                      ( 
                       SHF_INPUT.STRYRMO(i) >= '09801' 
                       AND 
                       SHF_INPUT.STRYRMO(i) < '10900' 
                      )  
                    OR 
                     ( 
                      SHF_INPUT.STRYRMO(i) = '10901' 
                      AND 
                      SHF_INPUT.DESCHGCT01(i) = '081' 
                      ) 
                    ) 

                then   TEST.PMS_UTIL.STR_TO_DATE( SHF_INPUT.DESCHGCT01(i) || SHF_INPUT.DESCHGST01(i) )  

                else  TEST.PMS_UTIL.STR_TO_DATE( SHF_INPUT.DESCHGDT01(i) ) 
             end ),

            -- below are fields that will be filled later
            null ,  -- SOURCE_CALC ,
            SHF_INPUT.OID(i) ,
            null   -- GAIN_CURR_DT_CALC 
           )  ;

        EXIT WHEN L_OHF%NOTFOUND; -- exit when last row is fetched

      END LOOP;

      COMMIT;

      close L_OHF;

    END;
  end CONVERT_OHF_FA;

person Marc    schedule 27.03.2014    source источник
comment
Что душит, когда вы делаете SELECT INTO? Вы хотите сказать, что это занимает слишком много времени? Если да, то каков план запроса? Сколько строк в таблице PMS_OHF? У скольких есть OID <= '000052000'? Индексируется ли oid?   -  person Justin Cave    schedule 28.03.2014
comment
Кроме того, достаточно ли вы проследили код, чтобы определить, сколько времени тратится на выполнение различных функций по сравнению с тем, сколько времени тратится на чтение из источника или запись в место назначения? Как выглядят ваши события ожидания? Можно ли использовать параллелизм?   -  person Justin Cave    schedule 28.03.2014
comment
Судя по вашему уменьшенному примеру, есть ли особая потребность в PL/SQL? Любая причина, по которой вы не можете просто вставить /*+ добавить */ в новую_таблицу, выбрать col1,col2,some_transform_function(col3),some_other_transform_function(col4) from old_table; Это, безусловно, было бы самым эффективным решением. Если бы все преобразования можно было выполнять в функциях, которые можно применять на лету, это был бы правильный путь.   -  person Mark J. Bobak    schedule 28.03.2014


Ответы (2)


execute immediate 'alter session enable parallel dml';
INSERT /*+ APPEND PARALLEL */ INTO TEST.OPTM_SHIST(...)
SELECT ...
FROM TEST.PMS_OHF
WHER OID <= '000052000';

Это способ выполнять большие загрузки данных. Не дайте себя обмануть всеми причудливыми опциями PL/SQL, такими как массовый сбор, конвейерные таблицы и т. д. Они редко бывают быстрее или проще в использовании, чем старый добрый SQL. Основное преимущество этих функций заключается в повышении производительности мучительного построчного процесса без значительного рефакторинга.

В этом случае похоже, что логики в PL/SQL уже практически нет. Почти весь PL/SQL можно выкинуть и заменить одним запросом. Это значительно упрощает модификацию, отладку, добавление параллелизма и т. д.

Некоторые другие советы:

  1. ORDER BY, вероятно, бесполезен для загрузки данных. Если только вы не пытаетесь сделать что-то необычное с индексами, например улучшить фактор кластеризации или перестроить без сортировки.
  2. Убедитесь, что ваши функции объявлены как DETERMINISTIC, если вывод всегда идентичен для одного и того же ввода. Это может помочь Oracle избежать вызова функции для того же результата. Для еще большей производительности вы можете встроить все функции в оператор SQL, но это может привести к путанице.
  3. Если вам все же нужно использовать BULK COLLECT, используйте подсказку APPEND_VALUES, а не APPEND.
person Jon Heller    schedule 28.03.2014
comment
Верхушка айсберга. Пришла из старых дней плоских файлов мейнфреймов, xformed программистами, не работающими с БД. Скорее делайте большинство в прямом SQL. 1-й шаг — запустить их код, доказать, что мы можем сопоставить устаревшие результаты. Во-вторых, убедить их, что требуется полная переработка. Массовое перестроение нескольких таблиц, все с (ненужными) ORDER BY. Xform fcns прост, более поздние fcns нужны DETERMINISTIC - плохой дизайн. Разве для BULK COLLECT не потребуется: переменная TYPE TABLE столбца% type для каждого из 168 исходных столбцов? Это был мой последний вывод... я ошибаюсь? - person Marc; 29.03.2014
comment
Это то, что я пробовал, а затем вызывал столбцы в ROWTYPE, но продолжал получать ORA-00913: слишком много значений. Я предположил, что неправильно обрабатывал столбцы - последний раз, когда я получил это, было на fcn, который вызывался в SELECT, получая при вызове более 1 строки. Если я МОГУ использовать %ROWTYPE, я буду очень счастлив... - person Marc; 29.03.2014
comment
Эта ошибка означает, что в списке SELECT есть дополнительный столбец. Со 170 столбцами отследить ошибку может быть сложно! - person Jon Heller; 29.03.2014
comment
Я пройдусь по списку еще раз... Я делал это несколько раз, пункт за пунктом, сопоставляя порядок и тип. Я начал со списка столбцов определения таблицы Oracle (целевой), сбросил в XLS, затем целевой список INSERT, затем список VALUES, но я сделаю это снова. Кроме того, я попробую с подмножеством столбцов и посмотрю, позволит ли это мне сделать частичную спецификацию, а затем добавить больше, если это возможно. - person Marc; 30.03.2014

После того, как я бросил это для других проблем, я снова взял это сегодня.

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

И тут меня осенило... мой индекс был не в том месте.

НЕПРАВИЛЬНАЯ форма:

    SHF_INPUT.ID(i) ,
    '00000000000000000000000' || SHF_INPUT.IOD(i) ,
    TEST.PMS_UTIL.STR_TO_YM_DATE( SHF_INPUT.STRYRMO(i) ) ,

ПРАВИЛЬНАЯ форма:

    SHF_INPUT(i).ID ,
    '00000000000000000000000' || SHF_Input(i).IOD ,
    TEST.PMS_UTIL.STR_TO_YM_DATE( SHF_Input(i).STRYRMO ) ,

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

Спасибо за вашу помощь и рекомендации.

person Marc    schedule 05.04.2014