Как оптимизировать использование конвейерного, слабо типизированного курсора ref

У меня возникли проблемы с процедурой; при запуске для «больших» наборов (800+ родителей, 1300+ детей) он очень медленный (30–60 секунд).

Основная идея состоит в том, чтобы получить все родительские записи (и их соответствующие дочерние элементы), соответствующие определенным критериям поиска, вместе с 3 дополнительными элементами информации, которые необходимо будет вычислить.

Мой подход к проблеме был

  1. для создания пользовательского типа записи с дополнительными полями для вычисляемых значений.
  2. Затем ссылка на этот тип записи может быть передана каждой функции, управляемой основной функцией обработки.
  3. Поскольку значение вычисляется для каждой родительской записи, прикрепите его к записи.

Каждая процедура GET_PARENT_RECORDS и GET_CHILD_RECORDS вызывается один раз за поиск, а каждая вычислительная функция запускается N раз (где N — количество родительских и/или дочерних записей).


Вопрос 1: правильный ли это подход? (слабо типизированные курсоры, конвейерные функции) Если нет, то как мне подойти к проблеме, предполагая, что я могу переделать?

Вопрос 2. За исключением полной перезаписи, есть ли что-то очевидное, что можно улучшить в предоставленном коде?

Вопрос 3. Может быть, что-то еще не так, так как я заметил, что один и тот же медленный запрос возвращается через 20 секунд, когда я запускаю процедуры несколько раз?


Определение пакета

create or replace
PACKAGE THIS_PKG AS

  Type parentCursor IS REF CURSOR;
  Type childCursor IS REF CURSOR;

  Type ParentRecordType IS RECORD (
    other_columns,
    Extra_column_A,
    Extra_column_B, 
    Extra_column_C,
    Row_num);    

  --associative array
  TYPE ParentArray IS TABLE OF ParentRecordType;

  FUNCTION processParents(
      p IN THIS_PKG. parentCursor
  )  RETURN ParentArray
  PIPELINED
  ;

  FUNCTION countSomething(some params…)
      RETURN INT;

  FUNCTION checkCondX (SomeParent IN ParentRecordType) 
      RETURN VARCHAR2;

  FUNCTION checkCondY (SomeParent IN ParentRecordType)
      RETURN VARCHAR2;

  PROCEDURE GET_PARENT_RECORDS( other_parameters, Parents OUT THIS_PKG.parentCursor);

  PROCEDURE GET_CHILD_RECORDS( other_parameters, Children OUT THIS_PKG.childCursor);

END THIS_PKG;

Тело пакета

-- omitted

FUNCTION processParents(
      p IN THIS_PKG.parentCursor
  )  RETURN ParentArray
  PIPELINED
  IS
      out_rec  ParentArray;
      someParent   ParentRecordType;
  BEGIN
    LOOP
        FETCH p BULK COLLECT INTO out_rec LIMIT 100;

        FOR i IN 1 .. out_rec.COUNT
        LOOP
        out_rec(i).extra_column_A := countSomething (out_rec(i).field1, out_rec(i).field2);
        out_rec(i).extra_column_B := checkCondX(out_rec(i));
        out_rec(i).extra_column_C := checkCondY(out_rec(i));
        pipe row(out_rec(i));
        END LOOP;

        EXIT WHEN p%NOTFOUND;
    END LOOP;
    RETURN;
  END processParents;

PROCEDURE GET_PARENT_RECORDS(
      some_columns,
      Parents OUT THIS_PKG. parentCursor) IS
  BEGIN   
      OPEN Parents FOR
      SELECT *
      FROM TABLE(processParents (CURSOR(
        SELECT *
        FROM (
              --some select statement with quite a few where clause 
          --to simulate dynamic search (from pre-canned search options)
       )
     ))) abc
      WHERE abc.extra_column_C like '%xyz%' --(xyz is a user given value)
      ;
END GET_PARENT_RECORDS;

Обновление Вчера провел некоторые исследования и наткнулся на Quest Batch SQL Optimizer (от Toad). Я подключил пакет и вот что я получил.

Результаты пакетной оптимизации Результаты пакетной оптимизации

Сложный запрос Сложный запрос

Проблемный запрос Проблемный запрос


person user1766760    schedule 03.04.2013    source источник
comment
Вы сделали какое-либо профилирование, чтобы указать, в чем заключается проблема?   -  person Bob Jarvis - Reinstate Monica    schedule 04.04.2013
comment
@BobJarvis Я сомневаюсь, что смогу полностью профилировать, поскольку я не администратор базы данных, и мне может быть трудно использовать его время. Но я провел некоторый анализ запросов и приложил два плана объяснения. Это то, о чем ты говоришь?   -  person user1766760    schedule 04.04.2013
comment
Никогда не препятствуйте полной перезаписи, когда речь идет о SQL (или PL/SQL). Конвейерная обработка — замечательная функция, однако часто от нее можно избавиться с помощью улучшенного декларативного подхода.   -  person Michael O'Neill    schedule 09.11.2013


Ответы (2)


Что происходит в разделе обработки строк? Много времени может быть потрачено на эти функции countSomething, checkCondX/Y. Они также делают вызовы SQL? Я бы сначала проверил производительность табличной функции без дополнительных предикатов. Может быть, лучше просто создать запрос, который делает все это в SQL, а не в функциях - если вы можете сделать это, это будет намного быстрее, чем вызов функции для каждой строки.

    out_rec(i).extra_column_A := countSomething (out_rec(i).field1, out_rec(i).field2);
    out_rec(i).extra_column_B := checkCondX(out_rec(i));
    out_rec(i).extra_column_C := checkCondY(out_rec(i));

Также представленный вами план объяснения интересен, поскольку оптимизатор считает, что из всех таблиц возвращается только 1 строка (мощность 1). Если это не так, то план запроса не будет оптимальным. Может потребоваться сбор статистики, использование динамической выборки или количественные подсказки для табличной функции.

Наконец, загляните в DBMS_SQLTUNE.REPORT_SQL_MONITOR, который предоставляет подробный отчет о вашем sql. Если запрос динамически не определяется как требующий мониторинга, вам необходимо добавить подсказку /*+ MONITOR */. Это предоставляет дополнительные сведения, такие как количество возвращенных строк, количество выполнений и другие интересные сведения, недоступные в плане объяснения.

SELECT /*+ MONITOR */
FROM slow_query;

-- then run sqltune to get a report
SELECT *
FROM TABLE(DBMS_SQLTUNE.REPORT_SQL_MONITOR()); 
person Owen S    schedule 16.12.2014

Quest Batch SQL Optimizer (от Toad) или любой другой инструмент не сможет вам помочь, учитывая, что они не понимают, что вы делаете внутри функций. Проблема в "FETCH p BULK COLLECT INTO out_rec LIMIT 100;". Качество запроса, передаваемого в p, фактически определяет окончательный план выполнения и время выполнения. Конвейеризация не является причиной медлительности. Когда вы запускаете процедуру несколько раз, Oracle использует кэшированные данные. Мой лучший совет: используйте Java вместо PL/SQL для этой конкретной цели, это будет проще для понимания.

person Yuri Levinsky    schedule 23.12.2013