2015-05-15 8 views
7

Czy Oracle może ponownie wykorzystać wynik funkcji, gdy jest wywoływany w tym samym zapytaniu (transakcji?) Bez użycia pamięci podręcznej wyników funkcji?Wydajność Oracle: zapytanie wywołujące wiele identycznych wywołań funkcji

Aplikacja, z którą pracuję, jest silnie uzależniona od funkcji Oracle. Wiele zapytań kończy wiele razy wykonywanie tych samych funkcji.

Typowym przykładem może być:

SELECT my_package.my_function(my_id), 
     my_package.my_function(my_id)/24, 
     my_package.function_also_calling_my_function(my_id) 
    FROM my_table 
WHERE my_table.id = my_id; 

Zauważyłem, że Oracle zawsze wykonuje każdy z tych funkcji, nie zdając sobie sprawy, że ta sama funkcja została wywołana tylko sekundy temu w tej samej kwerendy. Możliwe, że niektóre elementy funkcji zostaną zbuforowane, co spowoduje nieco szybszy powrót. Nie dotyczy to mojego pytania, ponieważ chcę uniknąć całej drugiej lub trzeciej egzekucji.

Założono, że funkcje są dość zasobochłonne i że te funkcje mogą wywoływać więcej funkcji, opierając ich wynik na tabelach, które są rozsądnie duże i z częstymi aktualizacjami (milion rekordów, aktualizacje z powiedzeniem 1000 aktualizacji na godzinę). Z tego powodu nie można korzystać z pamięci podręcznej wyników funkcji Oracle.

Mimo że dane często się zmieniają, oczekuję, że wynik tych funkcji będzie taki sam, gdy zostaną wywołane z tego samego zapytania.

Czy Oracle może ponownie wykorzystać wyniki tych funkcji i w jaki sposób? Używam Oracle11g i Oracle12c.

Poniżej jest przykład (tylko przypadkowa funkcja non-sense do zilustrowania problemu):

-- Takes 200 ms 
SELECT test_package.testSpeed('STANDARD', 'REGEXP_COUNT') 
    FROM dual; 

-- Takes 400ms 
SELECT test_package.testSpeed('STANDARD', 'REGEXP_COUNT') 
    , test_package.testSpeed('STANDARD', 'REGEXP_COUNT') 
    FROM dual; 

używanych funkcji:

CREATE OR REPLACE PACKAGE test_package IS 

FUNCTION testSpeed (p_package_name VARCHAR2, p_object_name VARCHAR2) 
RETURN NUMBER; 
END; 
/

CREATE OR REPLACE PACKAGE BODY test_package IS 

FUNCTION testSpeed (p_package_name VARCHAR2, p_object_name VARCHAR2) 
RETURN NUMBER 
IS 

    ln_total NUMBER; 

BEGIN 

    SELECT SUM(position) INTO ln_total 
     FROM all_arguments 
    WHERE package_name = 'STANDARD' 
     AND object_name = 'REGEXP_COUNT'; 

    RETURN ln_total; 

END testSpeed; 

END; 
/
+0

Można nadal używać buforowania wynik i „relies_on” słowo kluczowe do obsługi zmian w tabeli - http://www.oracle.com/technetwork/issue-archive/2010/10-sep/o57plsql-088600.html Dlaczego nie wywołać funkcji raz używając klauzuli WITH, a następnie odwołać się do zapytania? – OldProgrammer

+0

W 12c można zadeklarować funkcję WEWNĄTRZ instrukcji SQL, która jest czymś BARDZO różniącym się od PL/SQL (z perspektywy transakcyjnej). – ibre5041

+0

Używanie klauzuli WITH nie pomaga, ponieważ nadal dwa razy wykonuje tę funkcję. Nie widzę, jak słowo kluczowe RELIES_ON zmienia cokolwiek, w zależnościach 11gR2 są automatycznie identyfikowane, a tabela wielokrotnie zmienia dane, unieważniając pamięć podręczną. Szukam specjalnie dla buforowania w ramach tego samego zapytania. Klauzula 12c WITH FUNCTION jest interesująca i mógłbym ją zastosować tu i tam, ale nie rozwiązuje większego problemu, chociaż funkcje z zagnieżdżonymi funkcjami są naprawdę skomplikowane i wykorzystują framework aplikacji, w której pracuję. – jmuntingh

Odpowiedz

4

Dodaj widok śródliniowy i ROWNUM, aby zapobiec ponownemu zapisaniu zapytania przez Oracle w jednym bloku kwerendy i wielokrotnym uruchomieniu funkcji.


funkcja Sample i demonstracja problemu

create or replace function wait_1_second return number is 
begin 
    execute immediate 'begin dbms_lock.sleep(1); end;'; 
    -- ... 
    -- Do something here to make caching impossible. 
    -- ... 
    return 1; 
end; 
/

--1 second 
select wait_1_second() from dual; 

--2 seconds 
select wait_1_second(), wait_1_second() from dual; 

--3 seconds 
select wait_1_second(), wait_1_second() , wait_1_second() from dual; 

Proste zmiany zapytań, które nie działają

Obie te metody nadal brać 2 sekundy nie 1.

select x, x 
from 
(
    select wait_1_second() x from dual 
); 

with execute_function as (select wait_1_second() x from dual) 
select x, x from execute_function; 

Zmuszanie Oracle wykonać w określonej kolejności

Trudno powiedzieć Oracle „wykonać ten sam kod, nie należy wykonywać żadnych orzecznik pchanie, łączenie, lub innych przekształceń na nim”. Istnieją wskazówki dla każdej z tych optymalizacji, ale są one trudne w użyciu. Istnieje kilka sposobów na wyłączenie tych transformacji, a dodanie dodatkowego ROWNUM jest zwykle najłatwiejsze.

--Only takes 1 second 
select x, x 
from 
(
    select wait_1_second() x, rownum 
    from dual 
); 

Trudno dokładnie zobaczyć, gdzie funkcje są oceniane. Ale te plany wyjaśniające pokazują, w jaki sposób ROWNUM powoduje, że widok wierszowy działa osobno.

explain plan for select x, x from (select wait_1_second() x from dual); 
select * from table(dbms_xplan.display(format=>'basic')); 

Plan hash value: 1388734953 

--------------------------------- 
| Id | Operation  | Name | 
--------------------------------- 
| 0 | SELECT STATEMENT |  | 
| 1 | FAST DUAL  |  | 
--------------------------------- 

explain plan for select x, x from (select wait_1_second() x, rownum from dual); 
select * from table(dbms_xplan.display(format=>'basic')); 

Plan hash value: 1143117158 

--------------------------------- 
| Id | Operation  | Name | 
--------------------------------- 
| 0 | SELECT STATEMENT |  | 
| 1 | VIEW   |  | 
| 2 | COUNT   |  | 
| 3 | FAST DUAL  |  | 
--------------------------------- 
+0

Wow, to bardzo nieoczekiwane.I na przykład działa! Nie pomaga jeszcze mój problem z "większym obrazem", ale zdecydowanie mogę tego użyć w niektórych przypadkach. Jednej rzeczy jednak, nie rozumiem - na wszystkich - dlaczego dodanie ROWNUM zmienia plan wyjaśniający/sprawia, że ​​Oracle wykonuje funkcję tylko raz. Czy istnieje głębsze wytłumaczenie tego? Pomoc jest bardzo doceniana, dzięki! – jmuntingh

+0

ROWNUM zwraca kolejność, w której wiersz został zwrócony, więc musi to być ostatni krok wykonania. Każdy rodzaj transformacji zmieniłby kolejność, więc Oracle nie może zmodyfikować bloku zapytania za pomocą ROWNUM. (Teoretycznie optymalizator potrafił rozpoznać, kiedy ROWNUM nie był używany, ale jest to rzadki problem i złamałby tylko dużo kodu, który wykorzystuje tę sztuczkę). –

+0

Wspaniały, nigdy bym o tym nie pomyślał. Zamierzam oznaczyć to jako najlepszą odpowiedź, ponieważ nie sądzę, że istnieje rozwiązanie "buforowania" w tej samej transakcji. Pamięć podręczna wyniku operacji jest prawdopodobnie najbardziej zbliżona do tego. Dzięki! – jmuntingh

1

można spróbować słowa kluczowego deterministic oznaczyć funkcje jak czysta . To, czy faktycznie poprawia to wydajność, to kolejne pytanie.

Aktualizacja:

Nie wiem, w jaki sposób realistyczny powyżej Twój przykład jest, ale teoretycznie zawsze można spróbować ponownie struktura programu SQL więc wie o powtarzających się funkcji połączenia (faktycznie powtórzone wartości). Podobne do:

select x,x from (
    SELECT test_package.testSpeed('STANDARD', 'REGEXP_COUNT') x 
     FROM dual 
) 
+3

Nie, to nie jest funkcja deterministyczna. nie zwraca tego samego wyniku dla tych samych danych wejściowych za każdym razem. – OldProgrammer

+0

@OldProgrammer jest fuction nie deretministic, to masz kłopoty. Ponieważ firma Oracle nie gwarantuje, że zamówienie zostanie ocenione. Więc nigdy nie wiesz, co otrzymujesz. Wywołanie SQL z FUNCTIONs, które są wywoływane z SQL, jest antykreśleniem. To z kolei prowadzi do niebezpiecznych przekrętów danych. Wewnętrzny kod SQL działa w innym kontekście transakcji i może widzieć dane fantomowe. – ibre5041

+0

Funkcja nie może być deterministyczna, ponieważ definicja determinizmu uzyskuje ten sam wynik dla tych samych parametrów. W tym przypadku jednak parametry mogą być takie same, ale podstawowe dane mogą ulec zmianie, co powoduje inną wartość zwracaną. Ponadto, nawet dodając deterministyczny, nie robi różnicy. Wciąż wykonuje to dwa razy. – jmuntingh

0

Użyj widoku w linii.

with get_functions as(
SELECT my_package.my_function(my_id) as func_val, 
    my_package.function_also_calling_my_function(my_id) func_val_2 
    FROM my_table 
WHERE my_table.id = my_id 
) 
select func_val, 
    func_val/24 as func_val_adj, 
    func_val_2 
from get_functions; 

Jeśli chcesz wyeliminować wywołanie dla pozycji 3, zamiast tego prześlij wynik func_val do trzeciej funkcji.

+0

Nie rozumiem tego, to nic nie zmienia ... Wciąż kończy się wykonywanie wszystkich funkcji niezależnie. Dzięki za pomoc! EDYCJA: Rozumiem, co masz teraz na myśli, to nie jest to, o co myślę. Moja wina jako mojego przykładu jest bardzo uproszczonym wyjaśnieniem problemu. Zobaczę, czy później mogę dodać bardziej realistyczny przykład/wyjaśnienie mojego problemu. – jmuntingh

Powiązane problemy