Oracle SQL - Bind variables (zmienne bindowane) vs literals (łańcuchy znaków)
Instancja bazy Oracle przechowuje w swoich przepastnych, ale skończonych zasobach pamięci ;) mnóstwo ciekawych informacji. Do wielu z nich mamy dostęp przez widoki v$. Ich listę możemy przejrzeć w specjalnych perspektywach dictionary oraz fixed_table.
select * from dictionary; select * from fixed_table;
Jedną z ciekawszych informacji, którą można wydobyć z tych perspektyw to lista aktualnych zapytań do bazy danych (historyczne również są do "wydobycia").
select * from v$sql; /* lub */ select * from v$sqlarea;
Odpowiednie grupowanie pozwala na sprawdzenie czy przypadkiem pamięć nie przechowuje 2 różnych zapytań z takim samym planem wykonania.
select sql.plan_hash_value, count(*) ile from v$sql sql group by sql.plan_hash_value having count(*) > 1 order by ile desc;
Sytuacja w której w/w zapytanie coś zwróci pośrednio świadczy o tym, że parsowanie odbywało się na zapytaniu, w którym zamiast zmiennych bindowanych programiści dynamicznie przyklejali konkatenacją wartości wyszukiwane do słów kluczowych. Np
'select t.id, t.wartosc from tabelka t where t.id' || '>' || to_char(100)
Aby sprawdzić co to za SQL postawiamy plan, który odnaleźliśmy powyżej do kolejnego zapytania
select sa.sql_text from v$sql_area where plan_hash_value = ...;
Konsekwencje takiego lenistwa są straszne, baza danych "myśli", że otrzymała całkowicie nowe zapytanie i wrzuca je do parsowania. Proces parsowania zapytań jest bardzo kosztowny, szczególnie dla skomplikowanych i długich zapytań.
Ostatecznie po parsowaniu okaże się, że plan zapytania jest identyczny jak w jakimś poprzednio analizowanym przypadku, jednak na to instancja bazy już wpływu nie ma. W efekcie pamięć zapełnia się bezsensownymi wpisami a procesor zajmuje się w kółko przeliczaniem tego samego.
Sytuacja ma jeszcze jeden minus. Zapychanie pamięci SGA (konkretnie Shared Pool) przeznaczonym na SQL-e tym samym zapytaniem powoduje, że wypadają z niej zapytania rzadziej wykonywane, często bardzo kosztowne.
Proces development-u powinien uwzględniać poniżej opisane sytuacje cyklicznie wykonywanych tych samych zapytań.
Niepoprawny pod względem wydajności cykl wykonywania podobnych zapytań
CONNECT PARSE BIND EXEC FETCH DISCONNECT CONNECT PARSE BIND EXEC FETCH DISCONNECT ...
Wydajny na bazie Oracle cykl wykonywania podobnych zapytań
CONNECT
PARSE
BIND
EXEC
FETCH
BIND
EXEC
FETCH
...
DISCONNECT
SQL Injections
Kolejną przewagą bind-a nad literałami jest bezpieczeństwo danych. Zapytanie ze zmiennymi bindowanymi jest parsowane przed podstawieniem wartości. Co to daje? Jeśli jakiś osobnik próbowałby podstawić do zapytania
select count(*) from appuser where user='xyz' and password='abc';
coś w stylu
select count(*) from appuser where user='xyz' and password='abc' or '1'='1';
To w przypadku binda zupełnie się to nie sprawdzi. Parsowanie nastąpi wcześniej niż postawienie tych danych i najzwyczajniej baza będzie przeszukiwać kolumnę password szukając ciągu: abc' or '1'='1. Wynikiem zapytania będzie 0 więc w tym przykładzie bardzo uproszczonej weryfikacji użytkownika nie udało by się dostać do konta użytkownika xyz.
cursor sharing
Istnieje możliwość wymuszenia użycia planów zapytania dla podobnych zapytań nawet gdy zmienne nie są bindowane a wysyłane do bazy jako literały (sklejone z resztą). Mechanizm ten nazywa się cursor_sharing.
alter system set cursor_sharing = force;
Opcja ta wymusi, że przed procesem parsowania wszystkie literały zostaną przekształcone w parametry :1 :2 itp. I dopiero wtedy baza sprawdzi sobie czy już takiego zapytania przypadkiem nie było.
Niestety baza nie potrafi wtedy stwierdzić czy jest to rozwiązanie optymalne. Jeśli bowiem zmienną była płeć to plan zapytania dla kobiet może być zupełnie inny niż dla mężczyzn (np w populacji żołnierzy służby zasadniczej). Efekt może okazać się katastrofalny pod względem przyrostu czasu wykonania w tego typu zapytaniach.
W bazie Oracle 10g domyślnie jest ustawione exact czyli, żeby nie odbyło się parsowanie zapytania powinny być identyczne
CURSOR_SHARING = EXACT
W efekcie posługiwanie się literałami przy budowaniu ma niewiele wspólnego z wydajnością. Z przejrzystością kodu źródłowego może być podobnie...
Dość ciekawym pomysłem jest ustawienie CURSOR_SHARING w opcji SIMILAR - wtedy optymaliztor Oracle w pseudo-inteligentny sposób będzie lawirował pomiędzy EXACT a FORCE. Jednak wiąże się to z bardzo silnym obciążeniem procesora a to kłóci się z wydajnością. No chyba, że mamy spory zapas na CPU. Wtedy można spróbować
Oracle PL/SQL
Pozytywnym aspektem całej sprawy jest to, że wszystkie literały używane w procedurach pl/sql-owych są automatycznie zamieniane na zmienne bindowane co w znaczący sposób podnosi komfort programowania.
2010.03.28 11:48:35.