способы избежать глобальных временных таблиц в Oracle - PullRequest
22 голосов
/ 27 мая 2010

Мы просто преобразовали хранимые процедуры нашего сервера sql в процедуры оракула. Sql Server SP сильно зависели от сеансовых таблиц (INSERT INTO #table1...), и эти таблицы были преобразованы в глобальные временные таблицы в Oracle. Мы получили около 500 GTT для наших 400 SP

Теперь мы обнаруживаем, что работа с GTT в oracle считается последним вариантом из-за производительности и других проблем.

какие есть еще альтернативы? Коллекции? Курсоры?

Наше типичное использование GTT выглядит так:

Вставить в GTT

INSERT INTO some_gtt_1
  (column_a,
   column_b,
   column_c)
  (SELECT someA,
      someB,
      someC
     FROM TABLE_A
    WHERE condition_1 = 'YN756'
      AND type_cd = 'P'
      AND TO_NUMBER(TO_CHAR(m_date, 'MM')) = '12'
      AND (lname LIKE (v_LnameUpper || '%') OR
      lname LIKE (v_searchLnameLower || '%'))
      AND (e_flag = 'Y' OR
      it_flag = 'Y' OR
      fit_flag = 'Y'));

Обновление GTT

UPDATE some_gtt_1 a
SET column_a = (SELECT b.data_a FROM some_table_b b 
               WHERE a.column_b = b.data_b AND a.column_c = 'C')
WHERE column_a IS NULL OR column_a = ' ';

и позже получить данные из GTT. Это всего лишь примеры запросов, на самом деле запросы действительно полны со множеством объединений и подзапросов.

У меня есть вопрос из трех частей:

  1. Может кто-нибудь показать, как преобразовать приведенный выше пример запросов к коллекции и / или курсоры?
  2. С с GTT вы можете работать изначально с SQL ... зачем уходить от GTT-таблицы? они действительно так плохи.
  3. Какими должны быть рекомендации по Когда использовать и когда избегать GTT

Ответы [ 2 ]

30 голосов
/ 27 мая 2010

Давайте сначала ответим на второй вопрос:

"Зачем уходить от ГТЦ? действительно так плохо. "

Пару дней назад я собирал подтверждение концепции, которая загружала большой XML-файл (~ 18 МБ) в XMLType. Поскольку я не хотел хранить XMLType постоянно, я попытался загрузить его в переменную PL / SQL (память сеанса) и временную таблицу. Загрузка его во временную таблицу заняла в пять раз больше времени, чем загрузка в переменную XMLType (5 секунд по сравнению с 1 секундой). Разница заключается в том, что временные таблицы не являются структурами памяти: они записываются на диск (в частности, ваше назначенное временное табличное пространство).

Если вы хотите кешировать много данных, то их хранение в памяти приведет к перегруженности PGA, что не очень хорошо, если у вас много сеансов. Так что это компромисс между оперативной памятью и временем.

К первому вопросу:

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

Запросы, которые вы публикуете, могут быть объединены в один оператор:

SELECT case when a.column_a IS NULL OR a.column_a = ' ' 
           then b.data_a
           else  column_a end AS someA,
       a.someB,
       a.someC
FROM TABLE_A a
      left outer join TABLE_B b
          on ( a.column_b = b.data_b AND a.column_c = 'C' )
WHERE condition_1 = 'YN756'
  AND type_cd = 'P'
  AND TO_NUMBER(TO_CHAR(m_date, 'MM')) = '12'
  AND (lname LIKE (v_LnameUpper || '%') OR
  lname LIKE (v_searchLnameLower || '%'))
  AND (e_flag = 'Y' OR
  it_flag = 'Y' OR
  fit_flag = 'Y'));

(я просто транспонировал вашу логику, но это выражение case() можно заменить более аккуратным nvl2(trim(a.column_a), a.column_a, b.data_a)).

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

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

create or replace type tab_a_row as object
    (col_a number
     , col_b varchar2(23)
     , col_c date);
/
create or replace type tab_a_nt as table of tab_a_row;
/

Вот пример функции, которая возвращает набор результатов:

create or replace function get_table_a 
      (p_arg in number) 
      return sys_refcursor 
is 
    tab_a_recs tab_a_nt; 
    rv sys_refcursor; 
begin 
    select tab_a_row(col_a, col_b, col_c)  
    bulk collect into tab_a_recs 
    from table_a 
    where col_a = p_arg; 

    for i in tab_a_recs.first()..tab_a_recs.last() 
    loop 
        if tab_a_recs(i).col_b is null 
        then 
            tab_a_recs(i).col_b :=  'something'; 
        end if; 
    end loop;  

    open rv for select * from table(tab_a_recs); 
    return rv; 
end; 
/ 

И вот оно в действии:

SQL> select * from table_a
  2  /

     COL_A COL_B                   COL_C
---------- ----------------------- ---------
         1 whatever                13-JUN-10
         1                         12-JUN-10

SQL> var rc refcursor
SQL> exec :rc := get_table_a(1)

PL/SQL procedure successfully completed.

SQL> print rc

     COL_A COL_B                   COL_C
---------- ----------------------- ---------
         1 whatever                13-JUN-10
         1 something               12-JUN-10

SQL>

В функции необходимо создать экземпляр типа со столбцами, чтобы избежать исключения ORA-00947. Это не обязательно при заполнении типа таблицы PL / SQL:

SQL> create or replace procedure pop_table_a
  2        (p_arg in number)
  3  is
  4      type table_a_nt is table of table_a%rowtype;
  5      tab_a_recs table_a_nt;
  6  begin
  7      select *
  8      bulk collect into tab_a_recs
  9      from table_a
 10      where col_a = p_arg;
 11  end;
 12  /

Procedure created.

SQL> 

Наконец, рекомендации

"Какими должны быть руководящие указания по использовать и когда избегать GTT "

Глобальные временные таблицы очень хороши, когда нам нужно разделить кэшированные данные между различными программными модулями в одном сеансе. Например, если у нас есть общая структура отчета, сгенерированная единственной функцией, питающей GTT, которая заполняется одной из нескольких процедур. (Хотя даже это может быть реализовано с помощью динамических ref-курсоров ...)

Глобальные временные таблицы также хороши, если у нас много промежуточной обработки, которая слишком сложна, чтобы ее можно было решить одним SQL-запросом. Особенно, если эта обработка должна применяться к подмножествам найденных строк.

Но в целом следует исходить из того, что нам не нужно использовать временную таблицу. Так

  1. Делайте это в SQL, если это не слишком сложно, в таком случае ...
  2. ... Делайте это в переменных PL / SQL (обычно в коллекциях), если в этом случае не требуется слишком много памяти ...
  3. ... Сделайте это с глобальной временной таблицей
3 голосов
/ 27 мая 2010

Обычно я бы использовал коллекцию PL / SQL для хранения небольших объемов данных (возможно, тысячи строк). Если бы объемы данных были намного больше, я бы использовал GTT, чтобы они не перегружали память процесса.

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

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

Вы можете использовать объекты коллекции уровня SQL как нечто, что легко переводится между SQL и PL / SQL

create type typ_car is object (make varchar2(10), model varchar2(20), year number(4));
/

create type typ_coll_car is table of typ_car;
/

select * from table (typ_coll_car(typ_car('a','b',1999), typ_car('A','Z',2000)));
MAKE       MODEL                           YEAR
---------- -------------------- ---------------
a          b                           1,999.00
A          Z                           2,000.00

declare
  v_car1 typ_car := typ_car('a','b',1999);
  v_car2 typ_car := typ_car('A','Z',2000);
  t_car  typ_coll_car := typ_coll_car();
begin
  t_car := typ_coll_car(v_car1, v_car2);
  FOR i in (SELECT * from table(t_car)) LOOP
    dbms_output.put_line(i.year);
    END LOOP;
end;
/
...