Как Oracle обрабатывает вызовы сохраненных функций в SQL?

ребята. Скажем, у меня есть запрос:

select t.value, my_stored_function(t.value)
  from my_table t
 where my_stored_function(t.value) = n_Some_Required_Value

Я переписал его следующим образом:

select value, func_value
  from (select t.value, my_stored_function(t.value) func_value 
          from my_table t) subquery
 where subquery.func_value = n_Some_Required_Value

Давайте считать my_stored_function ресурсоемким. Я предполагаю, что во втором запросе он вызывается в два раза реже, но существенного прироста производительности после этого изменения я не ощутил.

Так что, думаю, мое предположение было неверным. Как же тогда Oracle обрабатывает эти вызовы функций?


person Kirill Leontev    schedule 07.09.2010    source источник


Ответы (4)


Это действительно хороший вопрос.

Сначала я попытался создать таблицу и вставить образцы данных (только пять строк):

create table my_table(value number);
insert into my_table(value) values(1);
insert into my_table(value) values(2);
insert into my_table(value) values(3);
insert into my_table(value) values(4);
insert into my_table(value) values(5);

Я сделал простой тестовый пакет для проверки этого.

create or replace package my_package is
  g_counter_SELECT PLS_INTEGER := 0; -- counter for SELECT statement
  g_counter_WHERE  PLS_INTEGER := 0; -- counter for WHERE clause
  function my_function(number_in in number, type_in in varchar2) return number;
  procedure reset_counter;
end;
/

И тело...

create or replace package body my_package is
  function my_function(number_in in number, type_in in varchar2) return number is
  begin
    IF(type_in = 'SELECT') THEN
        g_counter_SELECT := g_counter_SELECT + 1;
    ELSIF(type_in = 'WHERE') THEN
        g_counter_WHERE := g_counter_WHERE + 1;
    END IF;
    return mod(number_in, 2);
  end;
  procedure reset_counter is
  begin
    g_counter_SELECT := 0;
    g_counter_WHERE := 0;
  end;
end;
/

Теперь мы можем запустить тест на Oracle 9i (на 11g такие же результаты):

-- reset counter
exec my_package.reset_counter();

-- run query
select t.value, my_package.my_function(t.value, 'SELECT')
  from my_table t
 where my_package.my_function(t.value, 'WHERE') = 1;

-- print result
exec dbms_output.put_line('Count (SELECT) = ' || my_package.g_counter_SELECT);
exec dbms_output.put_line('Count (WHERE) = ' || my_package.g_counter_WHERE);

Результат:

DBMS Output (Session: [1] SCOTT@ORA9i at: 08.09.2010 01:50:04): 
-----------------------------------------------------------------------
Count (SELECT) = 3
Count (WHERE) = 5

Вот план-таблица:

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |       |       |       |
|*  1 |  TABLE ACCESS FULL   | MY_TABLE    |       |       |       |
--------------------------------------------------------------------

Это означает, что функция (в расчетах WHERE) вызывается для каждой строки таблицы (в случае FULL TABLE SCAN). В операторе SELECT запускается ровно столько раз, сколько раз выполняется условие WHERE my_function = 1

Теперь... протестируйте второй запрос (те же результаты для Oracle9i и 11g)

Результат:

DBMS Output (Session: [1] SCOTT@ORA9i at: 08.09.2010 02:08:04): 
-----------------------------------------------------------------------
Count (SELECT) = 8
Count (WHERE) = 0

Объясните просто так (для режима оптимизатора ВЫБЕРИТЕ):

--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |       |       |       |
|*  1 |  TABLE ACCESS FULL   | MY_TABLE    |       |       |       |
--------------------------------------------------------------------

ВОПРОС: Почему Count (SELECT) = 8?

Поскольку Oracle сначала запускает подзапрос (в моем случае с FULL TABLE SCAN, это 5 строк = 5 вызовов my_function в операторе SELECT):

select t.value, my_package.my_function(t.value, 'SELECT') func_value from my_table t

И затем для этого представления (подзапрос похож на представление) запустить 3 раза (из-за условия, где subquery.func_value = 1) снова вызвать функцию my_function.

Лично я не рекомендую использовать функцию в предложении WHERE, но я допускаю, что иногда это неизбежно.

Как наихудший возможный пример этого иллюстрирует следующее:

select t.value, my_package.my_function(t.value, 'SELECT')
  from my_table t
 where my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
   and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
   and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
   and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
   and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE');

Где результат для Oracle 9i:

Count (SELECT) = 5
Count (WHERE) = 50

А в Oracle 11g это:

Count (SELECT) = 5
Count (WHERE) = 5

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

person Martin Mares    schedule 08.09.2010
comment
ха! Самое смешное, что если я запрашиваю `выберите t1.value из (выберите t.value, my_package.my_function(t.value, 'ГДЕ') fv из my_table t) t1, где t1.fv = 1` (БЕЗ фактического перечисления мой столбец fv в поле вывода) - остается на пять. Я думал, что oracle просто передает результат вызова функции уровня подзапроса на верхний уровень, и это происходит, но довольно странным образом. where t1.fv = 1 не вызывает функцию снова, но делает вывод t1.fv в списке выбранных столбцов. - person Kirill Leontev; 08.09.2010
comment
да, лучшее решение: выберите t.value, n_Some_Required_Value из my_table t, где my_stored_function(t.value) = n_Some_Required_Value; - person Martin Mares; 08.09.2010
comment
проблема в том, что на самом деле я должен проверить, не равно ли my_stored_function нулю, и вывести его фактическое значение, если это не так:)) - person Kirill Leontev; 08.09.2010
comment
а как насчет этого (используйте его без функции в WHERE): ... где случай, когда t.value != 0 тогда t.value еще 0 end; - person Martin Mares; 08.09.2010
comment
В этом тесте вы использовали второй параметр функции, который различает два вызова. Так что это неверный тест того, что может произойти, когда два вызова идентичны. - person Dave Costa; 21.07.2011

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

Редактировать: Судя по тестированию Мартина, это не соответствует действительности. Теперь я должен вернуться и найти тесты, которые я провел много лет назад, которые заставили меня думать, что это так, и посмотреть, что я сделал неправильно. Немного о ФБР все еще верно. Я надеюсь.

Может быть небольшая разница в использовании памяти и, возможно, в точном плане, используемом оптимизатором, но я бы не подумал, что это будет существенно. Почти наверняка не против стоимости самого вызова функции.

Я вижу единственный способ оптимизировать это с помощью индекса на основе функций.

person Alex Poole    schedule 07.09.2010
comment
И чтобы индекс на основе функции был возможен, хранимая функция должна быть детерминированной. - person Nils Weinander; 07.09.2010

Простой тест:

create or replace function print_function(v1 number) return number is
begin
   dbms_output.put_line(v1);
   return v1;
end;
/

select print_function(ASCII(dummy)) as test
  from dual
 where chr(print_function(ASCII(dummy))) = dummy;

Результаты (при использовании 10 г):

      TEST
----------
        88

88
88

Вывод: Функция выполнялась отдельно в предложениях SELECT и WHERE.

person Allan    schedule 07.09.2010

Вы можете использовать прагмы PL/SQL, чтобы повлиять на то, как Oracle оптимизирует запрос, см. Прагма RESTRICT_REFERENCES

person Erich Kitzmueller    schedule 08.09.2010