Tuning SQL. Wersja Andrzej Klusiewicz

Podobne dokumenty
Administracja i programowanie pod Microsoft SQL Server 2000

Optymalizacja poleceń SQL Wprowadzenie

Optymalizacja. Plan wykonania polecenia SQL (1) Plan wykonania polecenia SQL (2) Rozdział 19 Wprowadzenie do optymalizacji poleceń SQL

Fazy przetwarzania polecenia SQL. Faza parsingu (2) Faza parsingu (1) Optymalizacja poleceń SQL Część 1.

SYSTEMY OPERACYJNE I SIECI KOMPUTEROWE

Block Change Tracking

Zawartość. Wstęp. Moduł Rozbiórki. Wstęp Instalacja Konfiguracja Uruchomienie i praca z raportem... 6

Optymalizacja poleceń SQL

QUERY język zapytań do tworzenia raportów w AS/400

startup pfile= '$HOME/admin/pfile/initDBx.ora'; create spfile from pfile= '$HOME/admin/pfile/initDBx.ora';

SQL w 24 godziny / Ryan Stephens, Arie D. Jones, Ron Plew. Warszawa, cop Spis treści

Rozproszone bazy danych 3

Oracle11g: Wprowadzenie do SQL

T-SQL dla każdego / Alison Balter. Gliwice, cop Spis treści. O autorce 11. Dedykacja 12. Podziękowania 12. Wstęp 15

Wykład XII. optymalizacja w relacyjnych bazach danych

Wydajność hurtowni danych opartej o Oracle10g Database

Uprawnienia, role, synonimy

Optymalizacja poleceń SQL Metody dostępu do danych

77. Modelowanie bazy danych rodzaje połączeń relacyjnych, pojęcie klucza obcego.

Plan. Wprowadzenie. Co to jest APEX? Wprowadzenie. Administracja obszarem roboczym

Tabela wewnętrzna - definicja

Zamówienia algorytmiczne

SQL (ang. Structured Query Language)

Ustalanie dostępu do plików - Windows XP Home/Professional

Autor: Joanna Karwowska

(a) T (b) N (c) N (d) T

Rozdział 17. Zarządzanie współbieżnością zadania

Instrukcja podwaja zarobki osób, których imiona zaczynają się P i dalsze litery alfabetu zakładamy, że takich osbób jest kilkanaście.

Oracle PL/SQL. Paweł Rajba.

Administracja i programowanie pod Microsoft SQL Server 2000

PRZESTRZENNE BAZY DANYCH WYKŁAD 2

Optymalizacja poleceń SQL

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Zarządzanie obiektami bazy danych Oracle11g

Instytut Mechaniki i Inżynierii Obliczeniowej Wydział Mechaniczny Technologiczny Politechnika Śląska

Oracle PL/SQL. Paweł Rajba.

Trigger jest obiektem związanym z tablicą, który aktywuje się gdy do tablicy następuje odpowiednie zapytanie.

1.5.3 Do czego słuŝą tymczasowe przestrzenie Zarządzanie plikami danych

060 SQL FIZYCZNA STRUKTURA BAZY DANYCH. Prof. dr hab. Marek Wisła

Oracle PL/SQL. Paweł Rajba.

Administracja i programowanie pod Microsoft SQL Server 2000

Paweł Rajba

KOMPUTEROWY SYSTEM WSPOMAGANIA OBSŁUGI JEDNOSTEK SŁUŻBY ZDROWIA KS-SOMED

dziennik Instrukcja obsługi

Programowanie w SQL procedury i funkcje. UWAGA: Proszę nie zapominać o prefiksowaniu nazw obiektów ciągiem [OLIMP\{nr indeksu}] Funkcje użytkownika

Systemy GIS Tworzenie zapytań w bazach danych

Cwiczenie 1. Wys wietlanie plano w wykonania polecen SQL

Uruchamianie bazy PostgreSQL

Podstawy technologii WWW

Memeo Instant Backup Podręcznik Szybkiego Startu

Wykład 8. SQL praca z tabelami 5

DECLARE VARIABLE zmienna1 typ danych; BEGIN

Audyt serwera bazy danych Oracle Database 12c

5.3. Tabele. Tworzenie tabeli. Tworzenie tabeli z widoku projektu. Rozdział III Tworzenie i modyfikacja tabel

1 Instalowanie i uaktualnianie serwera SQL Server

Optymalizacja wydajności SZBD

Struktura drzewa w MySQL. Michał Tyszczenko

Stosowanie indeksów ma swoje korzyści, ale bywa również kosztowne.

Laboratorium nr 4. Temat: SQL część II. Polecenia DML

Fizyczna struktura bazy danych w SQL Serwerze

Bazy danych. Bazy danych. Zapytania SELECT. Dr inż. Paweł Kasprowski.

Włączanie/wyłączanie paska menu

Wykład 8: klasy cz. 4

System Oracle podstawowe czynności administracyjne

Plan wykładu BAZY DANYCH II WYKŁAD 4. Co to jest kursor? Rodzaje kursorów

Rozdział 4 KLASY, OBIEKTY, METODY

Przydziały (limity) pojemności dyskowej

Instalacja SQL Server Express. Logowanie na stronie Microsoftu

STROJENIE BAZ DANYCH: INDEKSY. Cezary Ołtuszyk coltuszyk.wordpress.com

SQL SERVER 2012 i nie tylko:

Aliasy Select p.first_name, p.salary, j.job_title from employees p, jobs j where p.job_id=j.job_id;

5.4. Tworzymy formularze

Currenda EPO Instrukcja Konfiguracji. Wersja dokumentu: 1.3

AUREA BPM Oracle. TECNA Sp. z o.o. Strona 1 z 7

SQL - Structured Query Language -strukturalny język zapytań SQL SQL SQL SQL

Bazy danych TERMINOLOGIA

Pakiety podprogramów Dynamiczny SQL

Plan. Raport. Tworzenie raportu z kreatora (1/3)

Przestrzenne bazy danych Podstawy języka SQL

koledzy, Jan, Nowak, ul. Niecała 8/23, , Wrocław, , ,

Baza danych sql. 1. Wprowadzenie

Część I Tworzenie baz danych SQL Server na potrzeby przechowywania danych

Język PL/SQL Procedury i funkcje składowane

"Kilka słów" o strojeniu poleceń SQL w kontekście Hurtowni Danych wprowadzenie. Krzysztof Jankiewicz

Instrukcja użytkownika systemu medycznego

Blaski i cienie wyzwalaczy w relacyjnych bazach danych. Mgr inż. Andrzej Ptasznik

Oracle PL/SQL. Paweł Rajba.

IIIIIIIIIIIIIIIMMIMMIII

CREATE USER

Praca z systemem POL-on. Zaznaczanie toków do eksportu.

Zadania do wykonania na laboratorium

Monitorowanie wydajność w bazie Oracle11g

Zmiany funkcjonalne i lista obsłużonych zgłoszeń Comarch DMS , Comarch DMS i Comarch DMS

dr inż. Jarosław Forenc

Modele danych walidacja widoki zorientowane na model

Wprowadzenie do baz danych

Cwiczenie 2. Metody dostępu do danych

Przykłady najlepiej wykonywać od razu na bazie i eksperymentować z nimi.

Transkrypt:

Spis treści Architektura...2 Wprowadzenie do optymalizacji SQL...10 Wprowadzenie do optymalizatora kosztowego...17 Interpretacja planów wykonania...23 Tracing aplikacji...35 Metody dostępu do danych...47 Operatory złączeniowe...58 Star Transformation...64 Indeksy bitmapowe łączeniowe...71 Statystyki...75 Zmienne bindowane...80 SQL Tuning Advisor...82 Automatyzacja tuningu SQL...97 Zarządzanie planami SQL...103 Podpowiedzi optymalizatora kosztowego...107 1/119

Architektura Aby przystąpić do strojenia musisz drogi czytelniku poznać podstawowe struktury, oraz architekturę którą będziemy w pracy wykorzystywać. Baza danych a instancja Przede wszystkim należy rozróżnić bazę danych od instancji. Baza danych to zbiór informacji, w tym przypadku przechowywany pod postacią plików na dysku. Użytkownik nie może się podłączyć do bazy danych bezpośrednio, w połączeniu musi pośredniczyć instancja bazy danych. Instancja i SGA Baza danych przechowuje dane w plikach na dysku twardym i umożliwia dostęp do tych plików za pośrednictwem systemu operacyjnego. Aby umożliwić efektywne operowanie danymi, Oracle używa pamięci współdzielonej, dostępnej dla wszystkich użytkowników bazy danych, nazywanej Globalnym Obszarem Współdzielonym (SGA System Global Area). Instancja Oracle to właśnie wymienione wyżej uruchomione procesy tła oraz zalokowany globalny obszar współdzielony SGA. Użytkownik podłączając się do bazy danych, nie pobiera danych bezpośrednio z pliku bazy danych. Polecenie języka SQL wystosowane przez użytkownika trafia do odpowiedniego bufora w strukturze SGA, a następnie, po jego przetworzeniu i zanalizowaniu pobierane są bloki z pliku danych do obszaru SGA. Dopiero stąd informacja przekierowana zostaje do użytkownika. W przypadku, kiedy dane, do których odwołuje się użytkownik znajdują się już w obszarze SGA, pomijana jest faza pobierania danych z pliku i od razu informacja zwracana jest użytkownikowi. Relacyjna baza danych Oracle posługuje się standardowym językiem zapytań SQL, oraz posiada wbudowany wewnętrzny język tworzenia procedur składowanych PL/SQL. Od wersji 8i jako języka tworzenia procedur składowanych w bazach danych Oracle można używać również języka Java. 2/119

Architektura klient serwer bazy danych Oracle zapewnia wielu użytkownikom równoczesny dostęp do tej samej bazy. Operacje odczytu tych samych danych przez wielu użytkowników równocześnie nie powodują konfliktów ani niespójności. Inaczej rzecz się ma w przypadku modyfikacji tych samych danych. Wówczas mogłyby wystąpić niespójności. W celu zachowania spójności danych w czasie równoczesnej próby ich modyfikacji wprowadzono mechanizm transakcji, jako jednostki, w ramach, której użytkownicy wykonują swoje operacje w bazie danych. W sytuacjach konfliktowych system zarządzania bazą danych (DBMS) szereguje operacje różnych transakcji w taki sposób, aby nie powstały niespójności danych. Moment podłączenia się użytkownika do bazy danych jest jednocześnie początkiem sesji danego użytkownika. Trwa ona aż do momentu zakończenia pracy z bazą danych. Równolegle jeden użytkownik może mieć otwartych więcej niż jedną sesję. W ramach jednej sesji użytkownik może realizować jedną lub więcej transakcji, jedna po drugiej. Transakcja jest jednostką interakcji użytkownika z bazą danych i składa się z pojedynczych operacji realizowanych w bazie danych. Użytkownik realizuje swoje transakcje albo przez polecenia języka SQL, które kierowane są bezpośrednio do systemu zarządzania bazą danych (DBMS), albo pośrednio przy użyciu wcześniej przygotowanych aplikacji bazy danych, odwołujących się do DBMS. 3/119

Użytkownicy i schematy Kiedy użytkownik bazy danych tworzy obiekt, jednocześnie staje się jego właścicielem. Obiekty te tworzone są w tak zwanym schemacie użytkownika, czyli logicznej przestrzeni bazy danych. Schemat użytkownika jest tworzony automatycznie podczas definiowania użytkownika i posiada on unikalną nazwę, która jest identyczna z nazwą użytkownika. Nazwa schematu wykorzystywana jest do wskazania obiektów bazy danych stanowiących własność danego użytkownika. Odwołanie do obiektu znajdującego się w innym schemacie umożliwia następująca składnia: NAZWA_UŻYTKOWNIKA.NAZWA_OBIEKTU Nazwy obiektów muszą być unikalne w obrębie schematu. Dwóch użytkowników może posiadać obiekt o tej samej nazwie w swoich schematach, natomiast różnie będą się do nich odwoływać. Jeśli użytkownik Kowalski oraz Nowak mają w swoich schematach obiekty o nazwie Towary mogą się do nich odwoływać (przy założeniu że mają do tego uprawnienia) dodając nazwę użytkownika kolegi przed nazwą obiektu. Użytkownik Kowalski odnosząc się do swojego obiektu wywołuje go : TOWARY Jeśli zechce odnieść się do obiektu kolegi: NOWAK.TOWARY 4/119

Obiekty bazy danych W bazie danych Oracle istnieje wiele różnych obiektów zarówno do przechowywania samych danych, jak i pełniących funkcje wspomagające zarządzaniem tymi danymi. Wszystkie obiekty tworzone są przez użytkowników bazy danych. Użytkownik musi mieć jednak przypisane uprawnienie tworzenia danego rodzaju obiektu. W systemie Oracle można zdefiniować np. takie obiekty: Tabele służą do przechowywania danych. Indeksy są strukturami danych wykorzystywanymi do przyspieszania dostępu do danych. Tabele tymczasowe Służą do przechowywania danych potrzebnych na czas jednej transakcji lub jednej sesji oraz do wspomagania zarządzania tymi danymi. Widoki to logiczna struktura, wirtualna tabela wyliczana w locie, określona przez zapytanie SQL, umożliwia dostęp do podzbioru kolumn i wierszy tabeli lub tabel. Sekwencje Sekwencja to obiekt bazy danych generujący kolejne liczby. Sekwencje są stosowane przede wszystkim do tworzenia kolejnych wartości sztucznych kluczy podstawowych Wyzwalacz jest to procedura wykonywana automatycznie jako reakcja na pewne zdarzenia w tabeli bazy danych. Pakiety, procedury i funkcje i inne Tabela Tabela to nieuporządkowany zbiór rekordów, informacji tego samego typu. Pole to pojedynczy niepodzielny element informacji, zawierający: Nazwę identyfikującą pole, Typ określający zbiór wartości, które to pole może przyjąć, Wartość będącą informacją zapisaną w polu. Rekord to uporządkowany zbiór różnych pól. Kolumna to zbiór pól tego samego rodzaju z zakresu wszystkich rekordów 5/119

Globalny obszar pamięci SGA SGA (System Global Area) jest buforem pamięci alokowanym podczas startu instancji. Przechowuje informacje współdzielone przez różne procesy bazy danych. M.in przechowuje: Kopie danych z dysku (w buforze danych znajdującym się w SGA) Bufor dziennika powtórzeń Plany wykonania zapytań SQL Generalnie SGA dzieli się na kilka części. Bufor danych - dane do których sięgamy zapytaniem, nie zawsze muszą być czytane z dysku. Jeśli ktoś wcześniej sięgał do danych których nie było w tym buforze, dane te musiały zostać odczytane z dysku a następnie wrzucone do bufora danych. Dzięki temu kiedy następna osoba zażąda tych samych danych, nie będzie już konieczności odczytywania ich z dysku bo będą dostępne w buforze danych. O tym że czas operacji odczytu z pamięci jest nieporównywalnie krótszy od czasu operacji odczytu z dysku jest nieporównywalnie krótszy, chyba nie muszę nikogo przekonywać :). Działa to też w drugą stronę. Zmieniane czy dodawane dane też nie od razu pakowane są na dysk, są zmieniane najpierw w buforze danych, dzięki czemu są one szybciej dostępne. Oczywiście w pewnym momencie zapisuje je na dysk specjalny proces (DBWR) ale o tym nieco później. Wiadomo że nie wszystkie dane mogą tu być przechowywane choćby ze względu na pojemność takiego bufora. Jeżeli bufor się przepełni, usuwane są dane najdawniej wykorzystywane. Jeżeli jakieś dane znajdujące się już w buforze zostaną wykorzystane ponownie, trafiają na początek listy. O to dba nam proces serwera. Takie działanie się nazywa algorytmem LRU (Last Recently Used) Bufor dziennika powtórzeń - wszystkie zmiany danych są rejestrowane. Dzięki temu, nawet jeśli zmienione dane nie zostały jeszcze trwale zapisane na dysk, jesteśmy w stanie je odtworzyć. Istnieją specjalne pliki dziennika powtórzeń w których zapisywane są wszelkie zmiany, jednak informacja zanim tam trafi, trafia do bufora dziennika powtórzeń. 6/119

Bufor biblioteczny - jeśli zadasz jakieś zapytanie SQL do instancji, owo zapytanie musi najpierw zostać sprawdzone pod kątem składniowym, również czy masz odpowiednie uprawnienia do odczytu/zapisu konkretnych danych. Oracle musi też wymyślić jak wykonać dane polecenie tak by zostało ono zakończone jak najszybciej. W tym celu wymyśla kilka planów wykonania zapytania, a następnie wybiera najbardziej optymalny. Gdyby Oracle za każdym razem miał wykonywać te same operacje dla tych samych zapytań byłaby to strata czasu. Dlatego wykonuje je za pierwszym razem, a następnie wrzuca właśnie do bufora bibliotecznego. Dzięki temu jeśli inny użytkownik wykona to samo zapytanie, Oracle sięgnie sobie do bufora bibliotecznego w którym już ma opracowany plan wykonania zapytania, sprawdzone czy zapytanie jest poprawne składniowo i nie musi tego wszystkiego wykonywać od początku. Bufor słownika - w Oracle funkcjonują specjalne słowniki w których zapisane jest jakie w bazie znajdują się obiekty, jakie są pomiędzy nimi zależności i kto ma do nich uprawnienia. Do tych danych trzeba sięgać często, właściwie to przy każdym zapytaniu. Dlatego słowniki te są ładowane do pamięci, dzięki czemu jest do nich szybszy dostęp. Ponadto w SGA znajdują się jeszcze inne bufory: JAVA_POOL - czyli bufor służący do przechowywania kodu Java. W Oracle możemy przechowywać poza danymi również klasy Javy które w trakcie wykonania potrzebują pewnej przestrzeni pamięci - do tego służy właśnie bufor JAVA_POOL. LARGE_POOL - Opcjonalny bufor który jest wykorzystywany przy okazji dużych operacji dyskowych np. backupu danych. STREAMS_POOL - specjalny bufor wykorzystywany przy replikacji bazy danych. Bufor danych domyślnie ma jedną część - DEFAULT. W razie potrzeby możemy jeszcze go podzielić na część KEEP, RECYCLE, oraz nk BUFFER. Bufor KEEP -służy do przechowywania obiektów które chcemy trwale przechowywać w pamięci, w taki sposób by nie zostały "wypchnięte" przez nowe bloki danych. Przykładowo jeśli jakaś tabela jest wykorzystywana często, chcemy by zawsze był do niej szybki dostęp. W takim wypadku musimy stworzyć taki bufor i określić że ta tabela ma byc przechowywana w nim. Bufor RECYCLE - do tego bufora możemy załadować obiekty z których korzystamy sporadycznie i nie chcemy by zajmowały nam cenne miejsce w buforze DEFAULT. Tu również musimy włączyć korzystanie z tego bufora i określić że dany obiekt ma być przechowywany właśnie w nim. Bufor nk- Bufor do przechowywania obiektów o niestandardowej wielkości bloków. Nie będziemy się tym tutaj zajmować. 7/119

Obszar pamięci PGA Ponieważ procesy serwera aby wykonać dla nas jakieś czynności też potrzebują pewnej ilości pamięci, alokowany jest dla każdego z nich obszar PGA. To specjalny wydzielony kawałek pamięci w którym proces serwera może : Sortować dane Trzymać informacje o sesji użytkownika Przechowywać informacje o zmiennych, tablicach, kursorach, danych przetwarzanych przez proces. Oczywiście w momencie zakończenia sesji użytkownika bufor ten nie jest już potrzebny i zaalokowana dla niego pamięć jest zwalniana. 8/119

Struktura logiczna Tablespace Baza danych podzielona jest na logiczne struktury zwane tablespace.tworzy się je w np. celu izolacji danych dwóch systemów. Istnieja predefiniowane przestrzenie tablespace : System, sysaux,temp, undo, example. Z każdym tablespace związany jest przynajmniej jeden fizyczny plik. Segment Obiekty np. tabele czy indeksy, przechowywane są pod postacią segmentów. Każdy segment podzielony jest na extenty. Extent Są składowymi segmentów. Na extenty składają się bloki. Bloki Bloki są najmniejszym elementem logicznym w bazie danych Oracle. Ich wielkość musi być zawsze wielokrotnością wielkości bloku dysku twardego. Ich domyślna wielkość to 8KB. Wielkość tą można ustawić podczas tworzenia bazy danych. 9/119

Wprowadzenie do optymalizacji SQL Niniejszy rozdział jest jedynie ogólnikowym wprowadzeniem do optymalizacji. Taki ogólny zarys, którego zasadniczym celem jest danie czytelnikowi poglądu na całość. Zasadniczo wszelkie optymalizacyjne problemy wynikają ze sposobu wykonywania zapytań. Zanim po wykonaniu zapytania otrzymamy z powrotem dane, musi zostać wykonane kilka czynności. Musi zostać sprawdzone czy obiekty do których się odwołujemy w ogóle istnieją i czy np. tabele posiadają kolumny do których chcemy uzyskać dostęp. W tym celu przeszukiwane są słowniki systemowe. Jeśli obiekty istnieją, musi zostać sprawdzone czy mamy uprawnienia do obiektów. Musi zostać sprawdzona poprawność składniowa zapytania. Muszą zostać sprawdzone dostępne struktury (np. indeksy) które można wykorzystać by pobrać dane. Musi zostać sprawdzone czy dane które chcemy pobrać znajdują się może w buforze db_cache. Muszą zostać wymyślone plany wykonania zapytania w oparciu o dostępne możliwości. Dla tych planów muszą zostać oszacowane koszty, które następnie są porównywane w celu wybrania najlepszego. Wreszcie dane muszą zostać pobrane, przetworzone i zwrócone. To w jaki sposób zapytanie jest wykonywane (myślę tutaj o algorytmach) zależy od kilku czynników. Na niektóre z nich możemy wpływać. O sposobie wykonania zapytania w nowszych wersjach Oracle decyduje optymalizator kosztowy. Opiera się on na informacjach statystycznych na temat obiektów do których odwołujemy się w zapytaniach. Dobiera odpowiednie algorytmy i metody dostępu do danych,w taki sposób, by w efekcie wykonanie zapytania jak najmniej obciążało serwer, a jednocześnie wyniki zapytań były zwracane jak najszybciej. Efektywność wykonywania zapytania zależy w dużej mierze od zastosowanych do pobrania danych algorytmów, ale również od innych czynników. Zdarza się też, że optymalizator kosztowy nie może zastosować wydajnego algorytmu z powodu źle zaprojektowanych lub ubogich struktur danych. Optymalizator kosztowy dobiera algorytmy m.in. na podstawie statystyk. Statystyki najogólniej mówiąc są to informacje na temat obiektów np. tabel. Zawierają wiadomości na temat np. ilości wierszy w tabeli, ich zróżnicowania. Będę starał się jak najwięcej tłumaczyć metodą analogii. Odnieśmy się do pobierania danych jak do podróży. Jeśli znajdujemy się w jakimś miejscu - dajmy na to w Gdyni i chcemy dostać się np. do Wrocławia musimy wybrać środek lokomocji ( odpowiedni algorytm dostępu do danych). W zależności od tego jaki wybierzemy, czas naszej podróży będzie krótszy lub dłuższy, oraz stopień naszego wysiłku poświęconego na dotarcie do celu będzie mniejszy lub większy. Wybierzemy oczywiście najszybszy i najmniej męczący sposób ( najmniej kosztowny explain plan plan wykonania zapytania złożony z wybranych algorytmów). Warunkiem który musi być spełniony by nasza podróż przebiegała wygodnie i szybko, jest przede wszystkim możliwość wyboru jakiegoś środka lokomocji. Wyobraźmy sobie, że nie ma takich połączeń kolejowych, lotniczych ani autobusowych którymi moglibyśmy dotrzeć na miejsce. Nie dysponujemy też żadnym pojazdem silnikowym. Ponieważ infrastruktura jest uboga i nasze możliwości są bardzo ubogie musimy wybrać najbardziej męczącą i najdłuższą, ale jednocześnie jedyną metodę pójść na piechotę. Znajduje to oczywiście odzwierciedlenie w optymalizacji. Jeśli nie zadbamy o to by dać optymalizatorowi kosztowemu właściwą strukturę (np. indeksy), nie będzie mógł wykorzystać 10/119

optymalnej metody dostępu do danych i wszędzie będzie chodził na piechotę. To właśnie powód dlaczego zadbanie o odpowiednie struktury jest tak istotne. Weźmy teraz do analizy inny przypadek. Znajdujemy się w pewnym miejscu. Chcemy dotrzeć do Wrocławia. Myślimy że znajdujemy się od niego 500 metrów więc podejmujemy spacer. Gdybyśmy znajdowali się 100km od Wrocławia, oczywiście wsiedlibyśmy w samochód i nim byśmy podróżowali. Co jednak gdybyśmy myśleli że znajdujemy się 500 metrów od celu a w rzeczywistości byli 10 km dalej? Wybralibyśmy nieoptymalny plan wykonania zapytania poszli na piechotę. W efekcie nasza podróż niepotrzebnie by się wydłużyła. Wszystko przez to, że podjęliśmy decyzję od doborze algorytmu na podstawie nieprawdziwej informacji. Wybrany plan wykonania zapytania byłby optymalny gdyby nasze przypuszczenia co do odległości okazały się prawdziwe. Taką informację o odległości do celu (ilości danych do pobrania/przetworzenia) optymalizator kosztowy podejmuje na podstawie statystyk. Jeśli więc nasze statystyki będą nieaktualne, np. będą zawierały informacje o małej ilości wierszy w tabeli a w rzeczywistości okaże się że tych wierszy jest znacznie więcej, optymalizator kosztowy wybierze właściwy algorytm dla małych porcji danych, ale zdecydowanie nie właściwy dla dużych. Z tego powodu musimy dbać o to, by statystyki były jak najświeższe i zawierały informacje zgodne z prawdą. Oczywiście statystyki zawierają znacznie więcej informacji niż tylko ilość wierszy w tabeli, o tym będzie w kolejnych rozdziałach. 11/119

Główne powody nieefektywnego wykonywania zapytań Przejrzyjmy więc kilka powodów nieefektywnego wykonywania się zapytań: Ubogie struktury Nieaktualne statystyki Wybór nieoptymalnego planu wykonania zapytania Źle skonstruowane zapytanie SQL. Ubogie struktury. Przykładowo brak indeksów powoduje pełne skany tabel, tam gdzie w rzeczywistości nie jest to potrzebne. Dodanie właściwych struktur, zmniejszy ilość odczytów z dysku, a także obciążenie procesora, co spowoduje poprawę wydajności zapytań. Nieaktualne statystyki. Nieaktualne statystyki powodują wybieranie przez optymalizator kosztowy nieodpowiednich planów wykonania zapytań, co efektuje spadkiem wydajności. Wybór nieoptymalnego planu wykonania zapytania. Najczęściej wynika z nieaktualnych statystyk, ale może też być spowodowany np. nie wykorzystywaniem istniejących struktur z innych powodów. Przykładowo indeks istnieje, ale jego status to INVALID (spowodowany np. przeniesieniem indeksu do innego tablespace), co sprawia że nie można z niego skorzystać. Źle skonstruowane zapytanie SQL. To może być np. wykorzystywanie UNION tam gdzie można zastosować UNION ALL, stosowanie HAVING tam gdzie można wykorzystać WHERE. Generalnie co do zasady konstrukcje zapytań powodujące niepotrzebne obciążenie. 12/119

Przykłady nieoptymalnie napisanych zapytań 1. Źle: select department_id, avg(salary) from employees group by department_id having department_id in (40,60,80); Dobrze: select department_id, avg(salary) from employees where department_id in (40,60,80) group by department_id; Wyjaśnienie Pierwsza wersja zapytania powoduje niepotrzebne wyliczanie średnich zarobków w departamentach. Wyliczenia te zostają usunięte po wyliczeniu. Lepszym rozwiązaniem jest najpierw usunięcie niepotrzebnych wierszy, a dopiero w dalszej kolejności wykonywanie wyliczeń. 2. Źle: select * from zamówienia_archiwalne union select * from zamówienia; Dobrze: select * from zamówienia_archiwalne union all select * from zamówienia; Wyjaśnienie: UNION powoduje sprawdzanie dwóch zbiorów pod kątem duplikatów. Jeśli wiemy że duplikatów na pewno nie ma, powinniśmy stosować UNION ALL. 13/119

3. Źle: select * from employees where salary/12>1000; Dobrze: select * from employees where salary>1000*12; Wyjaśnienie: Oczywiście lepiej jest, jeśli operacja matematyczna mnożenia zostanie wykonana raz, na kwocie 1000$, niż operacja dzielenia dla każdej wartości salary jaka pojawi się w tabeli. 14/119

Co więc możemy zrobić by zapytania na naszej bazie danych wykonywały się efektywnie? Poszukać tych zapytań które najbardziej obciążają Odświeżyć statystyki obiektów Odświeżyć statystyki systemowe Potworzyć indeksy tam gdzie są potrzebne lub odbudować tam gdzie nie działają efektywnie. Poprawić struktury tam gdzie to potrzebne. Nie zawsze jednak słaba wydajność wynika z w.w powodów. Możemy przecież mieć za mało pamięci RAM, słaby procesor lub nawet źle poustawiane bufory w SGA. Skąd możemy wiedzieć co jest przyczyną? By znaleźć pierwszy trop spójrz na poniższy wykres: Jeśli sposób wykonywania zapytań zbliżony jest do sytuacji A tj. zapytania nie trwają długo, ale bardzo obciążają serwer, to przyczyna leży raczej w strukturach i statystykach. Jeśli bliżej do B tj. zapytania wykonują się długo, ale za to obciążenie jest niskie to problem najprawdopodobniej wynika z słabego sprzętu lub złych ustawień buforów SGA. Wystarczy nawet nie dość szybki dysk twardy by zapytania długo trwały z powodu długiego oczekiwania na odczyt danych. Pamiętajmy też o tym, że wiele nieefektywnych zapytań obciążających procesor powoduje sytuację zbliżoną do B po prostu pozostałe zapytania muszą poczekać na zwolnienie zajętych zasobów. Może też zdarzyć się tak, że do utrzymania dostajemy system napisany przez innego dostawcę i system ten działa nieoptymalnie. Przykłady takich przyczyn: Złe zarządzanie połączeniami np. wielokrotne łączenie się i rozłączanie tam gdzie można by było wykorzystać jedną sesję. Nie wykorzystywanie możliwości jakie daje nam shared_pool, brak współdzielenia kursorów. Niepotrzebne wielokrotne wymuszanie parsowania zapytań dla których mógłby zostać wykorzystany ten sam plan wykonania. Nie dostatecznie częste zatwierdzanie transakcji, co powoduje długotrwałe blokady zasobów Wielkość plików dziennika powtórzeń jest zbyt mała i powoduje to częsty zrzut danych z db_cache na dysk. Niepotrzebne pobieranie całej zawartości tabeli, po to by potem wyświetlić z tego np. dwie kolumny. Pakiety skompilowane w trybie interpretted 15/119

Kilka wskazówek w projektowaniu struktur danych Twórz proste tabele jeśli to możliwe. Stosuj przemyślane zapytania SQL i nie pobieraj danych których nie potrzebujesz. Stosuj indeksy tylko tam gdzie to potrzebne i w taki sposób by miały szansę być wykorzystane (np. indeks podwójny jeśli zachodzi taka potrzeba zamiast indeksu pojedynczego). Stosuj klucze obce i główne, by dane były spójne. Dzięki temu nie będzie duplikatów i śmieciowych danych. Wykorzystuj struktury takie jak widoki zmaterializowane czy tabele tymczasowe, jeśli mogą one zastąpić wielokrotne niepotrzebne wykonywanie podzapytań w kilku zapytaniach. Wykorzystuj tablice partycjonowane przy dużych wolumenach danych. Zakładaj indeksy na kluczach obcych. Zakładaj indeksy na często przeszukiwanych kolumnach Pisz SQL tak by wykorzystywał indeksy Pisz zapytania tak, by mogły wykorzystywać wcześniej stworzone plany wykonania. Zdaję sobie sprawę że wiele z wymienionych tutaj rzeczy będzie dla Ciebie niejasne. Potraktuj to jako checkpoint do późniejszej optymalizacji Twoich rozwiązań. Przy okazji następnych rozdziałów wszystko się wyjaśni. 16/119

Wprowadzenie do optymalizatora kosztowego Kolejne kroki wykonywania zapytania SQL Stworzenie kursora (CREATE CURSOR) Kursor jest uchwytem do przestrzeni przeznaczonej na informacje niezbędne do przetworzenia zapytania. Podczas wykonywania zapytania kursor będzie zawierał pointer do aktualnie przetwarzanego wiersza. Jeśli miałeś do czynienia z PL/SQL dane pobierane są w momencie fetchowania wiersza. Znajdziejsz tutaj wskaźnik na ten aktualnie pobierany wiersz. Kursory przetwarzane są liniowo wiersz po wierszu. Pointer będzie się przesuwał do kolejnego wiersza. Parsowanie zapytania (PARSE) Sprawdzane jest czy wcześniej zostało wykonane to zapytanie i czy można wykorzystać explain plan stworzony dla wcześniejszego zapytania. Explain plany będą znajdować się w shared_pool. Metodą analogii wracając do naszego przykładu z podróżą. Jeśli jechałem w zeszłym tygodniu z Gdyni do Krakowa, stworzyłem sobie mapkę na której zaznaczyłem w jaki sposób mam dojechać z miejsca na miejsce. Oznaczyłem postoje, spośród kilku możliwych tras wybrałem jedną. Jeśli zechcę po raz kolejny jechać tą trasą a mam jeszcze mapkę (explain plan znajduje się nadal w shared_pool) mogę ją wykorzystać. Tak samo jeśli przyjdzie do mnie kolega i poprosi o wypożyczenie mu mapki, udostępnię mu ją by mógł zmniejszyć sobie ilość pracy związanej z planowaniem podróży. Tak to działa przy wykonywaniu zapytania. Jeśli wykonam zapytanie i powstanie przy tym plan jego wykonania ( explain plan - zawierający informacje co gdzie leży i jak się do tego dobrać) to przy kolejnym wykonaniu tego samego zapytania mogę wykorzystać wcześniej stworzony plan. Zapytania innych użytkowników również mogą wykorzystać ten sam plan. Jeśli będę tym razem jechał z Łodzi do Krakowa, to mimo że cel jest ten sam trasa będzie się różnić i nie mogę wykorzystać wcześniej stworzonej mapki. Z tego właśnie powodu zapytania muszą być identyczne (co do spacji) by mogły współdzielić plan wykonania. W tym miejscu sprawdzana jest poprawność składniowa zapytania, fakt posiadania lub nie uprawnień do obiektów. Jeśli znalazł się w shared_pool plan wykonania który można wykorzystać, jest wykorzystywany. Jeśli go tam nie było, jest tworzony i wrzucany do shared_pool. Opisanie wyniku (DESCRIBE) Opisanie kształtu wyniku zapytania. W tym etapie sprawdzane jest jak będzie wyglądać wynik tj. jakie będzie zawierał kolumny i jakiego typu. Definicja (DEFINE) Definiowany jest typ danych oraz wielkość zmiennych używanych do pobierania wyniku zapytania. Podpinanie zmiennych (BIND) W tym miejscu wszelkie zmienne zamieniane są na wartości np. zmienne bindowane. Zauważ że sprawdzanie wartości zmiennych bindowanych odbywa się już po poszukiwaniu explain planu. Zapamiętaj to, bo będzie to ważne podczas jednej z technik optymalizacji. 17/119

Zrównoleglenie procesów (PARALLELIZE) Niektóre elementy zapytania mogą być od siebie niezależne. Jeśli tak jest, powstaje kilka osobnych procesów w których te elementy są wykonywane równolegle zamiast liniowo. Dzięki temu zapytanie wykonuje się szybciej. Wykonanie (EXECUTE) Baza ma już wszystkie niezbędne informacje, więc zapytanie jest wykonywane. Jeśli jest to delete lub update, blokowane są odpowiednie wiersze do czasu zakonczenia transakcji. Pobranie danych (FETCH) Dane są pobierane wiersz po wierszu do zdefiniowanych w etapie DEFINE zmiennych. Wynik zapytania zwracany jest w postaci tabeli. Zamknięcie kursora (CLOSE CURSOR) Odpinana jest referencja do obszaru pamięci przeznaczonego na kursor dzięki czemu może zostać ponownie wykorzystana. 18/119

Do czego służy optymalizator kosztowy? Optymalizator kosztowy opierając się o statystyki i dostępne struktury danych wytwarza plany wykonania zapytania. Bierze pod uwagę dostępne indeksy i obmyśla kilka możliwości dostępu do danych. Powrót do analogii z podróżą: siadamy i mając za zadanie dotrzeć do Krakowa sprawdzamy różne możliwości. Mamy więc możliwość dotarcia pociągiem, samolotem, samochodem, motorem. W przypadku tych 2 ostatnich mogę jeszcze wybrać kilka możliwych dróg. Kiedy już będę miał wszystkie możliwości, usiądę i policzę ile czasu, oraz jak bardzo mnie zmęczy dotarcie do celu z wykorzystaniem każdej z tych możliwości. Wybiorę oczywiście tą najszybszą i najmniej męczącą. Optymalizator kosztowy określi koszt dla każdego planu wykonania i wybierze ten plan który będzie miał najniższy koszt. W trakcie szacowania kosztów Twojego zapytania CBO (cost based optimizer optymalizator kosztowy) może przetworzyć Twoje zapytanie na analogiczne, ale inaczej skonstruowane i wyliczyć koszt również dla tego analogicznego. Optymalizator kosztowy określa podczas opracowywania planów 3 rzeczy. Koszt, selektywność i liczebność. Selektywność selektywność=ilość wierszy pobranych/całkowita ilość wierszy. Przykładowo z tabeli zawierającej milion wierszy pobierzemy 100 spełniających jakiś warunek. Informacje statystyczne na temat tabel znajdziesz w słownikach: dba_tables, dba_tab_statistics, dba_tab_cols_statistics. Optymalizator kosztowy sprawdza np. jaką część wierszy stanowią wiersze które spełniają warunek department_id=60. Informacje te sprawdza właśnie w statystykach oraz histogramach. Jeśli nie ma aktualnych statystyk dla tabeli wykorzystywany jest Dynamic Sampling. Dynamic Sampling Dynamiczne próbkowanie wykorzystywane jest gdy brak statystyk dla tabeli, lub gdy są one na tyle przestarzałe że nie można im ufać. Poziom próbkowania określa parametr optimizer_dynamic_sampling który możemy zweryfikować w słowniku v$parameter. Domyślnie ten parametr ustawiony jest na wartość 2. Dopuszczalne wartości mieszczą się w przedziale 0 do 2. 0 oznacza zupełne wyłączenie dynamicznego próbkowania. 1 próbkowanie stosowane jest tylko w przypadku pełnego odczytu z obiektu (np. z tabeli). 2 to włączenie dynamic sampling, wyższe stopnie określają coraz bardziej agresywne próbkowanie. Liczebność (cardinality) cardinality=selektywność (( a właściwie jej stosunek procentowy do wszystkich wierszy w obiekcie) * całkowita liczba wierszy Określa oczekiwaną liczbę wierszy zwrocona w wyniku zapytania. Koszt (cost) Jednostka niemetryczna. Określa szacunkową liczbę odczytów I/O niezbędną w celu wykonania zapytania. Wliczane są również operacje na pamięci operacyjnej, czy też cykle procesora niemniej ważne dla optymalizacji co ilość odczytów z dysku. W oparciu o tą wartość porównywane są plany wykonania. 19/119

Parametr CURSOR_SHARING Określa jakie rodzaje zapytań SQL mogą współdzielić te same kursory (np. plany wykonania). Zmieniamy go poleceniem : ALTER SYSTEM SET CURSOR_SHARING='wartosc'; Wartości jakie może przyjmować: Exact (domyślny): Kursory mogą współdzielić tylko zapytania o identycznej konstrukcji co do znaku. Ma znaczenie wielkość liter i spacje. Force: Pozwala na współdzielenie kursorów przez zapytania podobne (np. różniące się wielkością liter) o ile różnice w nich nie wpływają na sens zapytania. Similar: Jak wyżej, z tym ze różnice nie mogą wpływać na wybór planu wykonania. 20/119

Parametr OPTIMIZER_FEATURES_ENABLE Możesz zauważyć że na dwóch bazach danych wykonując to samo zapytanie na tych samych danych, a mając odświeżone statystyki na obu dostaniemy różne plany wykonania. To może wynikać ze zróżnicowania parametrów inicjalizacyjnych w tych dwóch bazach. W zalezności od tych ustawień oraz od wersji bazy, możemy mieć dostępne różne techniki automatycznej optymalizacji. Poniżej zestawienie niektórych możliwości w zależności od wersji bazy: Własność Wersje 9.0.0 do 9.2.0 10.1.0 do 10.1.6 10.2.0 do 10.2.0.2 11.1.0.6 Fast Full Scan po indeksie X X X X Zmienne bindowane X X X X Łączenie tabel po X indeksach X X X Dynamiczne próbkowanie - X X X Query Rewrite - X X X Pomijanie indeksów które są bezużyteczne (np. mają już zbyt wiele poziomów) X X X Automatyczne wyliczanie statystyk dla indeksów w momencie ich tworzenia - X X X Automatyczne modyfikowanie zapytań w oparciu o koszt X X X Wykorzystanie rozszerzonych statystyk do określania selektywności - - X - Ten parametr umożliwia nam ustawienie zgodności wykorzystywanych przez optymalizator technik dla konkretnej wersji bazy danych. 21/119

Sprawdzić wartość tego parametru możemy w ten sposób: SELECT NAME, VALUE FROM V$PARAMETER WHERE NAME='optimizer_features_enable'; zmienić: ALTER SYSTEM SET OPTIMIZER_FEATURES_ENABLE='10.1.0'; Po takiej zmianie optymalizator będzie stosował tylko własności dostępne dla danej wersji. 22/119

Interpretacja planów wykonania Czym jest plan wykonania? Jest zwracanym przez optymalizator kosztowy algorytmem dostępu do danych. Zawiera informacje na temat obiektów które muszą zostać wykorzystane, sposobu dostępu do nich, wykorzystanych algorytmów łączenia tabel, szacowanych kosztów tych dostępów etc. Przeglądanie planów wykonania Z użyciem SQL Developera Najprostszą metodą sprawdzenia planu wykonania dla zapytania jest wpisanie jego treści w polu edycji SQL Developera i naciśnięcie F10. Zobaczymy wtedy obraz jak powyżej. Te najbardziej zagnieżdżone elementy są elementami składowymi elementów piętro niżej w zagnieżdżeniu. Odnosi się to również do szacunkowych kosztów. 23/119

Z użyciem tabeli plan_table Jeśli chcemy, możemy plany wykonania gromadzić w tabeli plan_table. Wystarczy zapytanie poprzedzić frazą : explain plan set statement_id='nasza nazwa' for [treść zapytania] set statement_id jest po to, by rozróżnić elementy planów wykonania pomiędzy różnymi planami. Nie jest obligatoryjne, natomiast w przypadku więcej niż jednego zapytania testowanego w ten sposób pojawi się problem z rozróżnieniem elementów. Po wykonaniu tego polecenia plan wykonania naszego zapytania znajdzie się w tabeli plan_table. Możemy do niej zajrzeć, jednak wynik będzie co najmniej nieczytelny. Wykonałem też dwa przykłady wykonania explain planów jeden z ustawianiem statement_id, drugie bez. W sekcji statement_id mamy nulle. Co jeśli wykonamy tak kilka zapytań. Rozróżnimy ich elementy? 24/119

Poniżej przykład: 25/119

Odfiltrujmy tylko to co jest potrzebne: Co tutaj jest co: operation - rodzaj operacji options rodzaj dostępu do obiektu object_type rodzaj obiektu do którego się odwołujemy (index, tabela, tabela tymczasowa etc). object_name nazwa obiektu do którego się odwołujemy cost koszt (omawiany wcześniej) cardinality liczebność (omawiana wcześniej) bytes ilość bajtów które należy odczytać by pobrać dane cpu_cost ilość cykli procesora niezbędna do przeprowadzenia operacji io_cost ilość bloków bazodanowych jakie trzeba odczytaćw celu przetworzenia operacji. Teraz ten plan stał się znacznie czytelniejszy, podobny do tego prezentowanego przez SQL Developera, jednak zawierający znacznie więcej szczegółów. Słów kilka o samej tabeli plan_table. Jest to globalna tabela tymczasowa. W związku z tym, każdy użytkownik będzie w ramach swojej sesji widział co innego w tej tabeli. Musiała zostać stworzona z opcją ON COMMIT PRESERVE ROWS, ponieważ zawartość tabeli widoczna jest do końca sesji niezależnie od zatwierdzania i wycofywania transakcji. 26/119

Porównywanie planów wykonania z użyciem tabeli plan_table Przy użyciu tabeli plan _table możemy również porównywać konkurencyjne zapytania pod kątem ilości io, kosztów etc. Wystarczy w pierwszej kolejności stworzyć explain plany dla zapytań: Ponieważ interesują nas dane dla całości zapytania, a nie elementy składowe, pobieramy element o ID 0 dla każdego zapytania: Z powyższego porównania można łatwo wywnioskować, że pomimo przetwarzania podobnej ilości danych, zapytanie z użyciem funkcji analitycznej znacznie bardziej obciąża procesor. 27/119

Pakiet dbms_xplan Wykorzystując ten pakiet możemy zastąpić ręczne selecty po tabeli_plan table. Wykonujemy polecenie explain plan for. a następnie wywołujemy taką oto funkcję: SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY()); dzięki czemu uzyskujemy bardzo czytelny wynik: 28/119

Alternatywnie możemy też wywołać: SELECT * FROM TABLE( DBMS_XPLAN.DISPLAY(NULL,NULL,'ALL')); aby uzyskać jeszcze więcej informacji takich jak np. typ danych zwracanych kolumn. Mamy też możliwość (jako sys) obejrzenia planu wykonania jakiegoś uruchomionego wcześniej zapytania SQL. W tym celu najpierw zaglądamy do słownika V$SQL. Słownik ten zawiera zapytania dla których plany zapytania nadal rezydują w buforze bibliotecznym. select sql_id,sql_text from v$sql; 29/119

Następnie wybieramy zapytanie które nas interesuje, określamy jego SQL_ID i wykorzystujemy funkcję display_cursor do wyświetlenia planu tego zapytania: select * from table(dbms_xplan.display_cursor('4kbduf1v68tty')); 30/119

Autotrace W przypadku SQL Developera oraz SQL Plusa, możemy włączyć sobie jeszcze opcję autotrace. Opcja ta jest dostępna od bardzo dawna od wersji 7.3 Oracle. Umożliwia nam ona automatyczne wyświetlanie planów wykonania przy wykonywaniu zapytania. W przypadku SQL Developera wykonujemy: SET AUTOTRACE ON; a następnie uruchamiamy zapytania przy użyciu F5 zamiast F9. Jak widać poza danymi z zapytania widzimy również explain plan. W SQLPlusie wystarczy włączyć autotrace (set autotrace on) i wykonywać zapytania jak do tej pory. 31/119

Możesz też włączyć autotrace w ten sposób: SET AUTOTRACE ON STATISTICS; Aby to uczynić musisz jednak mieć świeże statystyki i nadaną rolę plustrace użytkownikowi z poziomu którego chcesz włączać taki autotrace. W przypadku takiego włączenia autotrace będziemy mogli zaobserwować statystyki związane z wykonaniem zapytania. Co jest co: recursive calls ilość zapytań wewnętrznych wykonanych w związku z wykonywaniem naszego zapytania. Wliczane tutaj są np. zapytania po słownikach systemowych. Consistent gets Kiedy wykonywany jest update na danych, dane sprzed zmiany lądują w przestrzeni undo. Do czasu kiedy zmiany nie zostaną zatwierdzone (commit) przez użytkownika zmieniającego, wszyscy widzą dane oryginalne pochodzące z przestrzeni UNDO. Consistent gets określa ilość bloków odczytanych w trybie consistent co oznacza że dane mogą (ale nie muszą) pochodzić z przestrzeni UNDO. Db block gets dane aktualne odczytanie w sposób inny niż w trybie consistent. Takie dane są czytane tylko przy rekursywnych wewnętrznych odwołaniach np. przy czytaniu nagłówków segmentów tabel. Physical reads ilość bloków odczytanych bezpośrednio lub z bufora db_cache. redo_logs ilość bajtów wykorzystanych w dziennikach powtórzeń na potrzeby tego zapytania. 32/119

Sorts czasami w niektórych metodach dostępu do danych wykorzystywane jest sortowanie. Przykładowo przy sort merge join. Bytes set via SQL*Net to client ilość bajtów przesłana do klienta Bytes received via SQL*Net from client ilość bajtów przesłana przez klienta. Gdybyśmy zechcieli wyłączyć AUTOTRACE, musimy wydać polecenie: SET AUTOTRACE OFF; 33/119

Widoki V$SQL_PLAN, V$SQL, V$SQL_PLAN_STATISTICS, V$SQLSTATS Wszystkie poniższe widoki dostępne są z poziomu użytkownika SYS. Widok V$SQL Zawiera listę zapytań dla których plany wykonania zapytania nadal znajdują się w buforze bibliotecznym (library_cache). Widok V$SQL_PLAN Zawiera plany wykonania zapytań nadal obecnych w library_cache. Przykład praktycznego wykorzystania, bardzo szczegółowe dane na temat zasobów zużywanych przez ostatnio zadawane zapytania: select sql_text, sql_id, module, operation,options,object_type, object_owner,object_name, cost,cardinality,bytes,cpu_cost,cpu_time,elapsed_time, io_cost, v$sql.physical_read_requests,v$sql.physical_read_bytes,physical_write_bytes from v$sql_plan JOIN V$sql using(sql_id); Widok V$SQL_PLAN_STATISTICS Zawiera statystyki dla zapytań których plany zapytania znajdują się w buforze bibliotecznym. Przykład praktycznego zastostowania, statystyki dotyczące ostatnio wykonywanych zapytań, m.in. ilość wierszy zwracanych przez zapytanie: select sql_text,last_load_time, sps.* from v$sql s join v$sql_plan_statistics sps on s.sql_id=sps.sql_id; 34/119

Tracing aplikacji Do czego to służy? Zawartość tego rozdziału stanie się użyteczna gdy będziemy chcieli obserwować co się dzieje z punktu widzenia wydajności. w naszej sesji, lub sesji wykorzystywanej przez aplikację którą zarządzamy. Dzięki tej opcji będziemy mogli przejrzeć jakie są wykorzystywane plany wykonania dla zapytań, oraz szczegółowe informacje statystyczne. Przypuśćmy, że nasze informacje na temat problemu wydajnościowego ograniczają się do zgłoszenia użytkowników aplikacja działa wolno. Możemy znać najlepsze techniki optymalizacji, ale jeśli nie jesteśmy w stanie wskazać problemu, to na pewno nie jesteśmy w stanie go rozwiązać. Tracing aplikacji służy szukaniu dziury w całym :), czyli określaniu źródła problemu. Możemy obserwować pojedyncze sesje, wszystkie sesje mające wspólny element (np. różne sesje tego samego użytkownika, albo mające ten sam identyfikator), a nawet całą bazę danych. Wyniki tracingu trafią do specjalnego pliku, który następnie będziemy analizować. Obserwacja całej bazy Włączenie obserwacji dla całej bazy ogranicza się do wywołania procedury database_trace_enable: EXECUTE DBMS_MONITOR.DATABASE_TRACE_ENABLE(TRUE,TRUE); Pierwszy parametr to waits, drugi binds. Parametr waits określa, czy mają być monitorowane opóźnienia takie jak np. oczekiwanie na dysk twardy. Przykładowo chcemy zmienić coś w bazie. Bloki które będziemy zmieniać muszą trafić do bufora db_cache. Muszą zostać zaczytane z dysku. Zanim to się jednak stanie, proces naszego update musi poczekać. To jest właśnie takie zdarzenie. Parametr binds określa czy statystyki zapytania z różną wartością jakiegoś parametru mają być traktowane razem (tj. jako jedno zapytanie), czy osobno dla każdej wartości parametru. Aby wyłączyć obserwację bazy należy wywołać: EXECUTE DBMS_MONITOR.DATABASE_TRACE_DISABLE(); Obserwacja własnej sesji Jeśli chcemy śledzić własną sesję włączamy po prostu: ALTER SESSION SET SQL_TRACE = TRUE; lub EXECUTE DBMS_SESSION.SET_SQL_TRACE(TRUE); Aby wyłączyć obserwację dla własnej sesji wywołujemy: ALTER SESSION SET SQL_TRACE=FALSE; lub 35/119

EXECUTE DBMS_SESSION.SET_SQL_TRACE(FALSE); Możemy też ustawić parametr TIMED_STATISTICS na true: ALTER SESSION SET TIMED_STATISTICS=TRUE; Dzieki temu będziemy mięli informacje na temat czasów wykonania poszczególnych elementów zapytania. Obserwacja innej sesji Możemy również obserwować inną sesję: EXECUTE DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION (SID, SERIAL#,TRUE FALSE); Sid i serial# są wartościami które jednoznacznie identyfikują pojedyńczą sesję. Możemy te wartości pobrać ze słownika v$session: W powyższym przypadku wyszukałem sobie sesję użytkownika HR. Gdybym zechciał obserwować jego sesję mogę wykonać: EXECUTE DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION(33,3,TRUE); Gdybyśmy zechcieli w wynikach śledzenia innej sesji uwzględnić również waitsy i wartości parametów, możemy skorzystać z innej procedury: EXECUTE DBMS_MONITOR.SESSION_TRACE_ENABLE(SESSION_ID=>33,SERIAL_NUM=>3, WAITS=>TRUE,BINDS=>TRUE); Wyłączenie śledzenia sesji wygląda tak: EXECUTE DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION(33,3,FALSE); lub EXECUTE DBMS_MONITOR.SESSION_TRACE_DISABLE(SESSION_ID=>33,SERIAL_NUM=>3); 36/119

Uzyskiwanie wyników Dla przykładu włączyłem śledzenie własnej sesji i uruchomiłem skrypt PL/SQL który wykonał zapytanie tysiąc razy, i tysiąc razy pobrał wszystkie wiersze z wyniku zapytania: Wyniki śledzenia zarówno mojej, jak i innej sesji znajdą się w pliku na dysku. Plik ten znajdzie się w katalogu określonym przez parametr USER_DUMP_DEST. Najlepiej jest przejść do tego katalogu i wyszukać właściwy plik po dacie utworzenia: 37/119

Istnieje też inna możliwość odnalezienia właściwego pliku. Z poziomu sesji która ma być obserwowana należy uruchomić polecenie dodające do sesji specjalny identyfikator. Identyfikator ten, będzie zawierał się w nazwie pliku, i dzięki temu łatwiej nam będzie odnaleźć właściwy plik. Istnieje oczywiście inna możliwość bezpośredniego wskazania właściwego pliku, ale wymaga to wykorzystania dodatkowego narzędzia (TRCSESS). Przerwałem sesję i nawiązałem nową, aby zacząć pisanie do nowego pliku, inaczej system kontynuowałby uzupełnianie starego. Do swojej sesji dodałem identyfikator to_ten_plik i ponownie uruchomiłem skrypt: Identyfikator ten znalazł się w nazwie utworzonego pliku. 38/119

Zawartość naszego pliku śladu jest nieczytelna dla człowieka, dlatego trzeba go przetworzyć do zrozumiałego pliku wynikowego. W tym celu wywołujemy narzędzie tkprof by przetworzyć ten plik śladu do postaci czytelnej dla człowieka: Pierwszy parametr to plik który ma zostać przetworzony, drugi to plik do którego mają zostać wyrzucone wyniki (zostanie utworzony). Warto też pamiętać o wyłączeniu obserwacji, ponieważ pliki śladu mogą urosnąć do nadspodziewanych rozmiarów :) Plik został utworzony, jednak będzie zawierał wykonywane zapytania posortowane chronologicznie wg wykonania. My chcielibyśmy, by na początku były te najbardziej obciążające, a nie wykonane najwcześniej. W tym celu trzeba będzie dodać jeszcze jeden parametr przy wywołaniu programu TKPROF. Poniżej screen pliku i jego zawartości. Widzimy że pierwsze zapytanie w pliku to jakieś zapytanie systemowe, a nie to które uruchomiłem. 39/119

Zanim przejdziemy do samego sortowania, muszę wyjaśnić trzy kolejne etapy wykonywania zapytania, ponieważ mają one ścisły związek z samym sortowaniem wyniku. Etap Parse Na tym etapie system sprawdza czy obiekt do którego się odwołujemy istnieje, czy mamy do niego uprawnienia, szuka też planu wykonania zapytanie w cache, a jeśli go nie znajduje tworzy. Execute Dla SELECTa na tym etapie określane są adresy które mają zostać pobrane, dla UPDATE, INSERT, DELETE są w tej chwili zmieniane dane w bazie. Fetch Tutaj następuje fizyczne czytanie danych z bazy. Nie istnieje przełącznik sortowania na zasadzie od najbardziej obciążających. Po pierwsze musimy wskazać o obciążenie w którym z trzech etapów nam chodzi, po drugie jakiego rodzaju obciążenie. Typy sortowania zawsze zaczynają się od któregoś z trzech ciągów: prs, exe, fch. Tym początkiem wskazujemy właśnie etap przetwarzania zapytania który nas interesuje. Odpowiednio: prs parse, exeexecute, fch fetch. Dalsza część przełącznika to rodzaj obciążenia np. CPU - czas procesora wykorzystany na danym etapie, DSK ilość odczytów dyskowych na danym etapie. Poniżej najczęściej wykorzystywane przełączniki: Opcja Opis prscnt Ilość parsowań zapytania prscpu Czas procesora wykorzystany na parsowanie Prsela Całkowity czas parsowania Prsdsk Ilość odczytów z dysku podczas parsowania Execnt Ilość wykonań Execpu Czas procesora wykorzystany na etap Execute Exeela Całkowity czas etapu Execute Exedsk Ilość odczytów z dysku podczas etapu Execute Exerow Ilość wierszy przetworzonych na etapie Execute Fchcnt Ilość pobrań danych Fchcpu Czas procesora wykorzystany na pobranie danych Fchela Czas całkowity pobierania danych Fchdsk Ilość odczytów dyskowych podczas pobierania danych Aby zastosować sortowanie, dopisujemy sort=nazwaprzelacznika przy wywołaniu programu TKPROF: 40/119

Tym razem nasz wynik jest posortowany i na początku pliku jest zapytanie które uruchamiałem: 41/119

Po kolei wszystkie elementy. Najpierw poniższa tabela: Widzimy tutaj różne parametry wykonania zapytania rozbite na trzy omawiane wcześniej etapy. Co oznaczają kolejne parametry: count ilość wykonań danego etapu cpu Czas (w sekundach i setnych sekundy) przetwarzania elapsed - Czas (w sekundach i setnych sekundy) trwania danego etapu disk ilość bloków odczytanych fizycznie z dysku na danym etapie rows - ilość wierszy przetworzonych na danym etapie. W przypadku UPDATE, INSERT i DELETE ilość wierszy pojawi się dla etapu EXECUTE. Niżej mamy plan wykonania danego zapytania: Przy każdym z elementów planu wykonania, mamy jeszcze parę parametrów: cr consistent reads tj ilość odczytów zarówno z bufora, jak i z dysku pr phisical reads tj. ilość fizycznych odczytów z dysku pw phisical writes tj. ilość fizycznych zapisów na dysku time czas w mikrosekundach wykonywania danego elementu card ilość przetworzonych na danym etapie wierszy. 42/119

Narzędzie TRCSESS Uwaga! Narzędzie TRCSESS jest dostępne jedynie w wersjach Standard i Enterprise. Nie ma go w Express Edition. Narzędzie TRCSESS odnajduje za nas właściwe pliki śladu dla wskazanych parametrów np. SIDa i SERIAL# sesji. Ustawienie parametru TRACEFILE_IDENTIFIER jest możliwe tylko dla własnej sesji, nie możemy go ustawić np. dla sesji działającego programu. W przypadku optymalizacji systemu którego kodu nie możemy zmieniać, staje się ono bardzo przydatne. Przeprowadzimy małe doświadczenie. Włączam obserwację podpiętej sesji: Z poziomu obserwowanej sesji symuluję obciążenie. Zauważ że jest to już inne zapytanie niż wcześniej (tak dla rozróżnienia w plikach wynikowych): 43/119

Podobnych obserwacji w ostatnich kilku chwilach mogłem ja lub inne osoby puścić kilka. Wielkość pliku oraz data utworzenia mogą więc być kiepskim wskaźnikiem. Korzystając z narzędzia TRCSESS wskazuję o jaką sesję chodzi (zauważ że w parametrze session został podany SID i SERIAL obserwowanej sesji). Parametr output to plik śladu do którego poszukiwane przez nas dane mają zostać przerzucone. Taki plik i tak będzie trzeba przetworzyć tak jak wcześniej narzędziem TKPROF. Ostatni parametr to położenie plików wśród których TRCSESS ma szukać danych o sesji która mnie interesuje: 44/119

W efekcie powstaje plik śladu we wskazanym miejscu: Owy plik w dalszej kolejności przetwarzam tak jak wcześniej TKPROFem: Powstaje plik wyjściowy, tym razem czytelny dla człowieka: 45/119

Mamy już efekt końcowy : Praktyczne użycie Szukanie problemów wydajnościowych z użyciem wyżej opisanych narzędzi staje się bardzo proste i możliwe do zamknięcia w kilku etapach: Określenie sesji którą chcemy obserwować i włączenie dla niej tracingu, lub włączenie tracingu całej bazy. Odczekanie aż zbierze się trochę danych do analizy. Pozwalamy systemowi pracować. Pozbieranie właściwych danych z plików śladu przy użyciu TRCSESS Przetworzenie wyników z jednoczesnym sortowaniem wg wybranego kryterium obciążenia przy użyciu narzędzia TKPROF. Analizowanie planów wykonania najbardziej obciążających zapytań i zastosowanie właściwych technik optymalizacyjnych tam gdzie jest to potrzebne. 46/119

Metody dostępu do danych Oracle w celu pobrania danych może zastosować różne metody. Może wykorzystywać przeszukiwanie tabel w całości, lub korzystać z indeksów w celu odnalezienia danych w tabelach. Poniżej zamieszczam listing różnych metod dostępu dostępnych dla tabel i dla indeksów. Tabele Full Table Scan Rowid Scan Sample Table Scan Indeksy Unique Scan Range Scan Full Scan Fast Full Scan Skip Scan Index Join Dane mogą zostać pobrane przy użyciu wymienionych wyżej metod, w zależności oczywiście od tego jakie możliwości mu damy (przypominamy sobie historię z trasą z Gdyni do Krakowa). W pierwszej kolejności omówię każdą z tych metod dostępu do danych. W żadnym wypadku nie pomijaj tego rozdziału! Znajomość tych metod i ich dobre zrozumienie jest bardzo ważne! 47/119

Full Table Scan To przeglądanie całej tabeli wiersz po wierszu w celu znalezienia informacji które nas interesują. Tego typu przeglądanie tabeli jest naprawdę mało wydajne. Wyobraź sobie że szukasz jednego wiersza. Pracownika o numerze 100, który znajduje się gdzieś w tabeli zawierającej ogólne milion wierszy. Aby go znaleźć tą metodą musisz przejrzeć całe te milion wierszy jeden po drugim aż nie znajdziesz właściwego. Czy to nie jest marnotrawstwo czasu i zasobów? Optymalizator kosztowy wybiera tą metodę dostępu do danych kiedy nie ma indeksów z których mógłby skorzystać, kiedy żądamy od bazy całej zawartości tabeli, lub gdy zapytanie jest skonstruowane tak że uniemożliwia skorzystanie z indeksu (na razie tym się nie przejmuj, wyjaśni się nieco później). Przeglądanie całej tabeli precyzując do znacznika High Water Mark tj. do miejsca do którego najdalej dochodziły kiedyś dane w tabeli. Jeśli np. skasujemy jakieś dane (przy użyciu DELETE) z tabeli, znacznik HWM zostaje na swoim miejscu. 48/119

Rowid Scan Przede wszystkim muszę Ci przybliżyć pojęcie rowid. Każdy wiersz w tabeli zawiera rowid, który reprezentuje fizyczną lokalizację danego wiersza na dysku. Możesz wyświetlić te wartości: Jeśli taki adres jest znany, Oracle może pominąć przeszukiwanie tabeli wiersz po wierszu i od razu dostać się do poszukiwanego wiersza korzystając z ROWID: Oczywiście rzadko kiedy stosuje się takie wyszukiwanie jak na powyższym obrazku. Taki adres (rowid) pobierany jest z indeksu najczęściej z indeksu i dopiero w kolejnym etapie następuje dostęp do tabeli z wykorzystaniem rowid. 49/119

Sample Table Scan Ten rodzaj dostępu do tabeli wykorzystywany jest do pobierania próbek danych. Zwraca losowe wiersze z tabeli. 50/119

Co to jest index? Jest obiektem bazodanowym niezależnym logicznie i fizycznie od tabeli. Pozwala uzyskać szybszy dostęp do danych. Indeksy zakłada się na kolumnę w tabeli, kilka kolumn naraz (do 32). Bez nich wszystko będzie działać, jednak indeksy pozwolą nam szybciej dostać się do danych. Indeksy przechowują wartości kolumn na które są nakładane oraz ROWID wiersza. Rodzaje indeksów B-tree : Najczęściej wykorzystywane indeksy. Wykorzystuje się je tam gdzie dane w kolumnach na które zakłada się taki indeks są dość mocno zróżnicowane. Przykładowo nr pesel w tabeli ze wszystkimi obywatelami Polski. Nie przechowuje wartości nullowych. Tworzenie: CREATE INDEX NAZWAINDEKSU ON NAZWATABELI(NAZWAKOLUMNY) Bitmapowe: Wykorzystuje je się tam gdzie jest małe zróżnicowanie danych np. nazwa województwa w tabeli ze wszystkimi obywatelami Polski. Przechowuje wartości nullowe. Dostępne są w wersji Enterprise. CREATE BITMAP INDEX NAZWAINDEKSU ON NAZWATABELI(NAZWAKOLUMNY) Złożone: Składają się z większej niż jedna ilości kolumn. Stosuje się je kiedy w warunku where, podczas łączenia tabel lub w grupowaniu występuje kilka kolumn naraz. Powinno się stosować w indeksie taką kolejność kolumn jak występuje w zapytaniach. CREATE INDEX NAZWAINDEKSU ON NAZWATABELI(NAZWAKOLUMNY1, NAZWAKOLUMNY2) Unikalne: Zakładane na te kolumny w których wartości są unikalne. Umożliwiają stosowanie unique scan. CREATE UNIQUE INDEX NAZWAINDEKSU ON NAZWATABELI(NAZWAKOLUMNY) Funkcyjne: Oparte na funkcjach które przetwarzają zawartość kolumn na które są nakładane. Stosuje się je kiedy w tabeli mamy dane nieprzetworzone, a wykorzystujemy przetworzone w zapytaniach. CREATE INDEX NAZWAINDEKSU ON NAZWATABELI(NAZWAKOLUMNY*2) Tabele zorganizowane indeksowo: To tabele o strukturze indeksu b-tree. Wykorzystuje się je często przy słownikach (w bazodanowym pojęciu). Taka tabela musi posiadać klucz główny. CREATE TABLE NAZWATABELI( KOLUMNA1 TYPDANYCH PRIMARY KEY, KOLUMNA2 TYPDANYCH ) ORGANIZATION INDEX; 51/119

Skany po indeksach Unique Scan Wiemy już że przeszukiwanie indeksów będzie szybsze niż przeszukiwanie tabeli. Wiemy też że indeksy przechowują rowidy wierszy. Jeżeli na kolumnie założymy indeks, Oracle będzie mógł go przeszukać pod kątem wartości przez nas podanych by odnaleźć rowid wiersza. Kolejność w tym przypadku jest taka: 1. Oracle przeszukuje indeks w poszukiwaniu wartości 100 2. Znajduje wartość w indeksie i odczytuje przypisany do tej wartości rowid. 3. Poprzez rowid (adres fizyczny wiersza na dysku) Oracle dostaje się do wiersza w tabeli. Ten rodzaj skanu wykorzystywany jest kiedy poszukujemy dane poprzez kolumnę z kluczem głównym, lub jeśli na kolumnie której dotyczy warunek nałożony jest indeks unikalny. 52/119

Range Scan Skan zakresowy, wykorzystywany jest kiedy stosujemy warunki typu < lub > na kolumnach na które założony jest index. 53/119

Full Scan Jest to liniowe przeszukiwanie całego indeksu. Po co jest to not null? Założony mamy index b-tree, a jak wspomniałem wcześniej taki indeks nie przechowuje wartości null! Co za tym idzie, jeśli nie zagwarantujemy optymalizatorowi że albo w kolumnie nie ma wartości null (kolumna z warunkiem not null) albo nie powiemy mu że ewentualne wartości null nawet jeśli występują to nas nie interesują (warunkiem użytym w tym przykładzie), to optymalizator nie mając pewności czy zwróci wszystkie dane będzie wykonywał full scan ale po tabeli. Odnosi się to również do innnych rodzajów skanów! 54/119

Fast Full Scan Wykorzystywany w przypadkach kiedy wszystkie informacje niezbędne do pobrania znajdują się w indeksie. W takim przypadku w ogóle nie ma potrzeby sięgania do tabeli. Skip Scan Wykorzystywane wtedy gdy podczas przeszukiwania indeksu można pominąć bloki, w których z całą pewnością nie znajdziemy poszukiwanych przez nas danych. W poniższym przykładzie założyłem indeks na kolumnach department_id i manager_id. W samym zapytaniu skorzystałem tylko z drugiej kolumny indeksu. Tylko druga kolumna indeksu została przeszukana pod kątem znalezienia ROWIDów wierszy spełniających warunek. Numery departamentów nie były użyte w warunku, dlatego bloki zawierające informacje o departamentach można było pominąć. 55/119

Łączenie indeksów Jeśli zażądamy danych które są rozłożone po dwóch indeksach, system może na potrzeby wykonania zapytania połączyć je (zjoinować). Dzieje się to jednak sporadycznie, głównie w sytuacjach kiedy obciążenie z łączenia ze sobą indeksów jest mniejsze niż alternatywny pełen skan po tabeli a taka sytucja może mieć miejsce przy bardzo szerokich tabelach. 56/119

Skany po tabelach indeksowych Zauważ że mimo że wykonuję pełny skan po tabeli, mam rodzaj skanu charakterystyczny tylko dla indeksów. Taka tabela zbudowana jest jak indeks (struktura drzewiasta). Dodatkową korzyścią z tworzenia tabeli zorganizowanych indeksowo, jest możliwość stosowania na nich skanów typowych dla indeksów np. unique scan. To sprawia że takie tabele użyteczne są np. przy słownikach. 57/119

Operatory złączeniowe Kiedy wykorzystujemy łączenie tabel w zapytaniach (joiny), łączenie ich może odbywać się na kilka sposobów. Poniżej ich lista i charakterystyka. Nested loops Działa to w oparciu o pętle zagnieżdżone. Podczas łączenia dwóch tabel bierzemy wiersz z jednej tabeli i przeszukujemy całą druga tabelę w poszukiwaniu odpowiedników. Stosowany najczęściej kiedy jedna z tabel jest mała. 58/119

Sort Merge Join W tym rodzaju łączenia pobierane są dane z obu tabel po kawałku i sortowane po kolumnach wg których łączymy tabele. Następnie posortowane są porównywane ze sobą. Samo sortowanie jest operacją kosztową, więc taki rodzaj łączenia spotkamy rzadko. Wykorzystywany jest kiedy sortowanie i tak jest wymagane w związku z samym zapytaniem, lub przy połączeniach nierównościowych (kiedy warunkiem łączenia jest inny operator niż = ). 59/119

Hash join Mniejsza tabela jest zamieniana w pamięci na tablicę hashową, druga jest hashowana i porównywana z pierwszą. 60/119

Cartesian Join Join kartezjański stosowany jest kiedy połączymy dwie tabele bez podania warunku łączenia. Następuje wtedy łączenie na zasadzie każdy z każdym. 61/119

Łączenia nierównościowe (OUTER) Łączenia nierównościowe występują w przypadku nadmiarowości wierszy po którejś ze stron. W powyższym przypadku system musi wyświetlić nazwy departamentów do których nie są przypisani żadni pracownicy. Oznacza to że należy również wyświetlić wiersze które nie spełniają równania. 62/119

Antijoin Antijoin zwraca wiersze które nie pasują do wartości zwracanych przez podzapytanie. 63/119

Star Transformation Star Transformation jest metodą łączenia tabel używaną w środowiskach hurtowni danych w celu podniesienia wydajności wykonywania zapytań. Ta metoda zastępuje tradycyjne sposoby łączenia tabel : nested loops, sort merge join i hash join. Metoda Star Transformation jest dostępna od wersji 8i Oracle. Schemat Star Schemat gwiazdy jest najprostszym schematem hurtowni danych. Nazwa pochodzi od konstrukcji samego schematu, przypomina ona nieco kształt gwiazdy. W tym schemacie mamy jedną tabelę faktu, stanowiącą centralny element schematu, oraz przynajmniej dwie tabele wymiarów będące rozszerzeniami tabeli faktu. W przypadku wykonania zapytania korzystającego z takich tabel, przeprowadzone zostanie łączenie każdej z tabel rozszerzeń z tabelą faktu. Tabele nie łączą się między sobą. Na powyższej ilustracji tabelę faktu stanowi tabela zamówienia, natomiast tabele rozszerzeń (wymiarów) stanowią wszystkie pozostałe. 64/119

Schemat Snowflake W bazach danych możemy się spotkać również ze schematami typu Snowflake, które są rozwinięciem schematu Star. 65/119

Star Query Ideą Star Transformation jest zmniejszenie ilości full table skanów dużych tabelach w tym przypadku na tabeli faktu sales. W typowych zapytaniach na strukturze gwiazdy duża tabela faktu jest łączona z wielokrotnie mniejszymi tabelami rozszerzeń. Tabela faktu ma zazwyczaj po jednym kluczu obcym dla każdej tabeli rozszerzenia. select ch.channel_class,cu.cust_city,t.calendar_year,sum(s.amount_sold) suma_sprzedazy from sales s,channels ch,customers cu, times t where s.time_id=t.time_id and ch.channel_id=s.channel_id and s.cust_id=cu.cust_id and channel_class in ('Direct','Internet') and cust_state_province='ca' and t.calendar_year ='1998' group by ch.channel_class,cu.cust_city,t.calendar_year; Bez stosowania Star Transformation, łączenie przebiega w taki sposób, że tabela faktu (w tym przypadku tabela Sales) jest łączona osobno z każdą z tabel. Mimo, że w efekcie końcowym zapytania zostanie wyświetlona bardzo nieznaczna część tabeli sales, przeprowadzany jest na niej full scan. Im większa jest tabela faktu, tym większa utrata wydajności. Przyjrzyjmy się sposobowi wykonania tego zapytania w tradycyjny sposób: 66/119

Tabela zawiera prawie milion wierszy: Sam wynik zapytania dotyczy jednak jedynie 11 wierszy. Oczywiście wymagane było połączenie tabeli sales z tabelami channels,times i customers, jednak wykonywanie w tym celu full scana jest bardzo nieefektywne. 67/119

Star Transformation Optymalizator kosztowy rozpoznaje takie struktury i stosuje dla zapytań na nich specjalnie zoptymalizowane plany wykonania przy użyciu metody Star Transformation. Aby jednak było to możliwe, musi zostać spełnione kilka warunków: na kluczach obcych tabeli faktu powinny zostać założone indeksy bitmapowe. W tym przypadku osobne indeksy bitmapowe powinny zostaćzałożone na kolumnach: channel_id,time_id,cust_id tabeli sales. Parametr STAR_TRANSFORMATION_ENABLED musi być włączony dla sesji lub bazy danych. Muszą być przynajmniej dwie tabele wymiarów i jedna tabela faktu. Statystyki wszystkich obiektów biorących udział w zapytaniu muszą być świeże. Kiedy te warunki będą spełnione, optymalizator kosztowy będzie automatycznie wybierał metodę Star Transformation która jest bardzo efektywna dla zapytań typu star query (tj. takich jak omawiane wcześniej). Dla użytkownika fakt zmiany metody wykonywania zapytania jest niezauważalny (poza przyspieszeniem :) ). Zastosowanie Star Transformation nigdy nie doprowadzi do odmiennych niż pierwotne wyników zapytania. Sposób działania Star Transformation W jaki sposób działa metoda Star Transformation? Przebiega zasadniczo w dwóch fazach. W pierwszej fazie, system stosuje filtry na tabeli rozszerzeń i określa wartości klucza głownego dla wybranych wierszy. W drugiej fazie mając już ograniczone dane z tabeli rozszerzenia, system przeszukuje indeksy bitmapowe założone na kluczach obcych tabeli faktu w celu wybrania tylko niezbędnych wierszy z tabeli faktu. Przeprowadzimy teraz testy. Włączam parametr star_transformation_enabled dla sesji i ponawiam generowanie planu wykonania dla tego samego zapytania: alter session set star_transformation_enabled=true; select ch.channel_class,cu.cust_city,t.calendar_year,sum(s.amount_sold) suma_sprzedazy from sales s,channels ch,customers cu, times t where s.time_id=t.time_id and ch.channel_id=s.channel_id and s.cust_id=cu.cust_id and channel_class in ('Direct','Internet') and cust_state_province='ca' and t.calendar_year ='1998' group by ch.channel_class,cu.cust_city,t.calendar_year; 68/119

69/119

Róznica polega przede wszystkim na sposobie dostępu do danych z tabeli faktu: Porównajmy z wcześniejszym sposobem dostępu: Należy pamiętać, że im większa jest tabela faktu, tym większy koszt generuje full table scan. Wynika to z większej ilości danych które trzeba odczytać. Im więc tabela faktu jest większa, tym bardziej opłacalna z punktu widzenia optymalizacji jest implementacja metody Star Transformation. Podczas wykorzystania metody Star Transformation, następuje dwukrotny dostęp do tabel rozszerzeń. Po jednym razie dla każdej z dwóch faz wykonywania tej metody. Jeśli optymalizator kosztowy uzna to za opłacalne z punktu opłacalności, system utworzy tabelę tymczasową zawierającą odfiltrowane dane i będzie z niej korzystał zamiast wielokrotnego dostępu do tabeli rozszerzenia. Testy były przeprowadzane na schemacie SH, który jest dostarczany jako schemat przykładowy razem z samą bazą. Wystarczy jedynie odblokować konto SH i ustawić mu hasło. 70/119

Indeksy bitmapowe łączeniowe Indeksy bitmapowe łączeniowe są wykorzystywane głównie w hurtowniach danych, często wraz z Star Transformation. Indeksy łączeniowe pojawiły się z wersją 9i Oracle. Takie indeksy pozwalają zmniejszyć ilość wykonywanych kosztownych operacji łączenia tabel. Taki indeks będzie przechowywał wcześniej wyliczone wyniki łączenia. Podczas zapytania z użyciem łączenia, będzie można użyć danych z indeksu, zamiast wykonywać operację łączenia. W indeksach bitmapowych każda unikalna wartość w kolumnie jest powiązana z mapą bitową, w której każdy bit reprezentuje jeden wiersz w tabeli. 1 oznacza że wartość występuje w danym wierszu, 0 że nie występuje. Indeksy takie zakłada się na kolumnach o małym zróżnicowaniu. Nie powinno się ich stosować na tabelach często modyfikowanych (tzn. na tabelach w których pojawiają się często nowe wiersze, kasowane są stare, lub zmienia się wartość kolumny na której założony jest taki indeks). Wiąże się to z obciążeniem wynikającym z konieczności aktualizacji takiego indeksu. Przyjrzyjmy się teraz praktycznemu zastosowaniu tego typu indeksów. Pracujemy na schemacie SH, który jest schematem przykładowym dostarczanym przy instalacji bazy. Wystarczy ten schemat odblokować. Sprawdźmy plan wykonania zapytania wymagającego łączenia tabel customers i sales. Większość obciążenia generowane jest przez obciążenia wynikającego z samego łączenia, lub operacji z łączeniem związanych: select count(*) from sales join customers using(cust_id) where cust_city='yokohama'; 71/119

Założymy teraz indeks bitmapowy łączeniowy i porównamy wyniki. Najprostszy taki indeks mógłby zawierać samo tylko złączenie: create bitmap index lacz1 on sales(c.cust_id) from sales s, customers c where s.cust_id=c.cust_id local; Słowo local na końcu jest wymagane wyłącznie w przypadku tabel partycjonowanych. W tym jednak przypadku stosujemy warunek na kolumnie cust_city, więc najlepiej byłoby gdyby ta kolumna również znalazła się w indeksie. W innym wypadku, w związku koniecznością dostępu do danych z tej kolumny, CBO decydowałby o nie używaniu naszego nowego indeksu. Stworzymy więc indeks zawierający nie tylko łączenie, ale również kolumnę cust_city z tabeli customers: create bitmap index lacz1 on sales(customers.cust_city) from sales, customers where sales.cust_id=customers.cust_id local; Gdybyśmy dostali komunikat o braku klucza głównego, należy uruchomić poniższe polecenie: alter table customers enable constraint customers_pk; Dotyczy to jednak jedynie powyższego przykładu w schemacie SH. Indeksy bitmapowe łączeniowe wymagają, by łączenie następowało z użyciem klucza głównego, a ten w tabeli curstomers istnieje, ale jest wyłączony. Porównajmy teraz plany wykonania zapytania i ich koszty: Znacznie mniejszy koszt wykonania tego zapytania z użyciem indeksu bitmapowego łączeniowego, wynika z faktu, że nie ma konieczności wykonywania łączenia ze sobą tabel i związanych z tym odczytów danych które są do tego potrzebne. Informacje o połączeniu są przechowywane w indeksie. Oczywiście w przypadku np. dodania wiersza do tabeli sales, trzeba będzie znaleźć wszystkie odpowiedniki po stronie drugiej tabeli i zaktualizować indeks. To wiąże się z generowaniem sporego obciążenia przy ładowaniu danych. 72/119

Weźmy teraz troszkę bardziej skomplikowany przypadek. Łączymy ze sobą trzy tabele liniowo, przy czym warunek filtrowania wierszy stosujemy na ostatniej. select count(*) from sales join customers using(cust_id) join countries using (country_id) where country_name='poland'; Ponownie pojawia nam się spore obciążenie wynikające z potrzeby łączenia tabel. Tworzę więc indeks bitmapowy łączeniowy, tym razem na łączeniu ze sobą trzech tabel: create bitmap index lacz2 on sales(countries.country_name) from sales, customers, countries where sales.cust_id=customers.cust_id and customers.country_id=countries.country_id local; 73/119

Sprawdźmy jak prezentuje się nam plan wykonania teraz: Ograniczenia indeksów bitmapowych łączeniowych nie da się założyć takiego indeksu na tabeli tymczasowej, ani tabeli typu IOT. W klauzuli FROM nie można wymienić tej samej tabeli dwukrotnie nie może to być indeks funkcyjny 74/119

Statystyki Czym są statystyki? W skrócie statystyki są zbiorem informacji statystycznych na temat tabel, kolumn i indeksów. Zawierają informacje o np. ilości wierszy w tabeli, ich zróżnicowaniu, największej i najmniejszej wartości występującej w kolumnie. W oparciu o te informacje, optymalizator kosztowy (CBO) podejmuje decyzje o algorytmie dostępu do danych (przypominamy sobie analogię do podróży i różnych środków transportu z rozdziału o wprowadzeniu do optymalizacji), szacuje koszty. Należy je odświeżać, bo w przeciwnym przypadku CBO opierając się o nieprawdziwe informacje pochodzące z przedawnionych statystyk podejmować będzie decyzje błędne odnośnie doboru algorytmów i metod dostępu do danych. Oracle odświeża statystyki podczas tak zwanego Maintance Window który domyślnie przypada od 22 do 6 rano. Generuje nowe statystyki dla tych obiektów które ich nie mają wygenerowanych, odświeża dla tych które mają status STALE. Status STALE otrzymywują gdy zmieni się więcej niż 10% wierszy.. W wersji 10g Oracle, automatyzacja generowania statystyk związana była z procedurą GATHER_STATS_PROG wywoływanym przez job o nazwie GATHER_STATS_JOB. Przy użyciu słowników : DBA_SCHEDULER_JOBS DBA_SCHEDULER_SCHEDULES możemy odnaleźć szczegóły dotyczące częstotliwości odświeżania statystyk. W wersji 11g job ten nie jest już widoczny. Informacje o szczegółach automatycznego generowania statystyk znajdziemy w słowniku DBA_AUTOTASK_CLIENT_JOB. Rodzaje statystyk Statystyki tabel Zawierają informacje o : ilości wierszy w tabeli ilości bloków zajmowanych przez tabelę średnia długość wierszy Słownik: DBA_TAB_STATISTICS Statystyki indeksów Zawierają informacje o : ilości poziomów w drzewie indeksu ilości unikalnych wartości (lub ich kombinacji) w kolumnie (lub kolumnach) na którą został założony indeks. Słownik: DBA_IND_STATISTICS 75/119

Statystyki systemowe Zawierają informacje o : wydajności I/O wydajności procesora Statystyki kolumn Zawierają informacje o: ilości unikalnych wartości w kolumnie ilości nulli w kolumnie średniej długości wartości w kolumnie maksymalnej wartości minimalnej wartości zróżnicowanie danych w kolumnie. Słownik: DBA_TAB_COL_STATISTICS Histogramy Same statystyki zawierają informacje podstawowe o zróżnicowaniu zawartości kolumn takie jak np. maksymalna czy minimalna wartość oraz ilości unikalnych wartości w kolumnie. Histogramy zawierają znacznie bardziej szczegółowe informacje o zróżnicowaniu. Znajdziesz tutaj np. informacje o częstotliwości występowania konkretnych wartości. Zasadniczo informacje dotyczące histogramów znajdziesz w tej samej tabeli co statystyki tj. DBA_TAB_COL_STATISTICS i dodatkowo w DBA_TAB_HISTOGRAMS. Generalnie tworzenie histogramów jest najbardziej czasochłonne podczas odświeżania statystyk. Dzięki histogramom możesz wywnioskować jaki rodzaj indeksu można nałożyć na daną kolumnę. Pamiętaj że indeks bitmapowy nakładamy tam gdzie zróżnicowanie w kolumnie jest małe, indeks btree tam gdzie zróżnicowanie jest duże. Jeśli się pomylisz, może się okazać że optymalizator kosztowy nie będzie chciał wykorzystywać indeksu. 76/119

Odświeżanie statystyk i histogramów W przypadku gdyby automatyczne dobowe odświeżanie statystyk nie wystarczyło, możemy odświeżać statystyki ręcznie. Jest to konieczne np. wtedy gdy tabela zmieniana jest bardzo często. Odświeżanie statystyk systemowych: EXECUTE DBMS_STATS.GATHER_SYSTEM_STATS; Odświeżanie statystyk indeksu: EXECUTE DBMS_STATS.GATHER_INDEX_STATS('nazwaschematu','nazwaindeksu'); Odświeżanie statystyk tabeli EXECUTE DBMS_STATS.GATHER_TABLE_STATS('nazwaschematu','nazwatabeli'); Takie wywołanie spowoduje odświeżenie statystyk i histogramów dla tabeli którą wskażemy, ale również dla indeksów założonych na kolumny w tej tabeli. Odświeżanie statystyk schematu EXECUTE DBMS_STATS.GATHER_SCHEMA_STATS('nazwaschematu'); Takie wywołanie odświeży statystyki i histogramy dla wszystkich tabel i wszystkich indeksów w schemacie. Odświeżanie statystyk całej bazy danych EXECUTE DBMS_STATS.GATHER_DATABASE_STATS; Wszystkie statystyki w całej bazie zostaną odświeżone. Baardzo czasochłonna operacja. 77/119

Kasowanie statystyk EXECUTE DBMS_STATS.DELETE_TABLE_STATS('hr', 'employees'); Blokowanie statystyk EXECUTE DBMS_STATS.LOCK_TABLE_STATS('HR','EMPLOYEES'); Używane jest gdy chcemy zablokować stan kiedy nie ma statystyk dla obiektów, a chcemy by zawsze wykorzystywał DYNAMIC SAMPLING, albo kiedy chcemy zatrzymać statystyki w pewnym momencie tak by nie były automatycznie odświeżane. Odblokowujemy w ten sposób: EXECUTE DBMS_STATS.UNLOCK_TABLE_STATS('HR','EMPLOYEES'); Jeśli zablokujesz statystyki dla tabeli, wszystkie statystyki zależne (np. statystyki indeksów) również zostaną zablokowane. Odtwarzanie statystyk Czasami odświeżanie statystyk idzie w złym kierunku, i chcielibyśmy przywrócić stan statystyk np. sprzed odświeżenia. Za każdym razem kiedy odświeżamy statystyki, ich poprzednia wersja jest zachowywana byśmy mogli przywrócić ich stan. Wykorzystujemy w tym celu procedurę restore_table_stats z pakietu dbms_stats: Execute Dbms_Stats.Restore_Table_Stats(Ownname=>'hr', tabname=>'employees',as_of_timestamp=>to_timestamp('05-12-2011 12:00:00','dd-mm-yyyy hh24:mi:ss')); Historia statystyk Zawsze możemy sprawdzić kiedy były generowane statystyki dla tabeli, dzięki czemu będziemy wiedzieli np. do którego miejsca przywrócić statystyki. Aby obejrzeć historię statystyk przeglądamy słownik DBA_TAB_STATS_HISTORY. SELECT * FROM DBA_TAB_STATS_HISTORY WHERE TABLE_NAME='EMPLOYEES'; 78/119

Parametr OPTIMIZER_DYNAMIC_SAMPLING Dynamiczne próbkowanie wykorzystywane jest gdy brak statystyk dla tabeli, lub gdy są one na tyle przestarzałe że nie można im ufać. Poziom próbkowania określa parametr optimizer_dynamic_sampling który możemy zweryfikować w słowniku v$parameter. Domyślnie ten parametr ustawiony jest na wartość 2. Dopuszczalne wartości mieszczą się w przedziale 0 do 2. 0 oznacza zupełne wyłączenie dynamicznego próbkowania. 1 próbkowanie stosowane jest tylko w przypadku gdy: jest przynajmniej jedna niezanalizowana tabela wykorzystywana w zapytaniu. Nieprzeanalizowana tabela nie posiada indeksów Poziom 2 to włączenie dynamic sampling w taki sposób by próbkował wszystkie nieprzeanalizowane tabele. 79/119

Zmienne bindowane W Oracle funkcjonują zmienne bindowane, powiązane z sesją. Możemy wprowadzić do takiej zmiennej wartość, a następnie wykonać zapytanie z użyciem tej zmiennej. Jakie to ma znaczenie przy strojeniu SQL? Jeżeli wykonujemy wiele podobnych zapytań: SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID=100; SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID=104; SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID=107; SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID=109; dla każdego z tych zapytań wymyślany jest osobny plan wykonania. To nie jest dobre z przynajmniej dwóch powodów: Oracle musi poświęcić czas i zasoby na analizę i opracowanie explain planu (planu wykonania). Zapełniana jest przestrzeń w shared_pool w sumie niemal identycznymi zapytaniami a właściwie to związanymi z nimi planami wykonania. Sytuacja taka jest dość powszechna. Wykorzystujemy słowniki, a te jako z reguły intensywnie eksploatowane powinny być wykorzystywane w sposób możliwie najbardziej optymalny. Poszukaj w słowniku v$sql zapytań o podobnej konstrukcji np. select * from v$sql where lower(sql_text) like '%from%employees%'; Dzięki temu sprawdzisz czy taka sytuacja ma również miejsce u Ciebie. Aby się przed tym uchronić możemy nieco zmodyfikować zapytania. Przede wszystkim tworzymy zmienną bindowaną : variable x number; następnie przypisz do niej wartość: begin :x:=100; end; Pamiętaj że nazwy zmiennych bindowanych przy przypisaniu i odwołaniu poprzedzamy dwukropkiem. Teraz możesz odwołać się do tej zmiennej w zapytaniach: SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID=:X; Pamiętaj że wartość zmiennej możesz zmieniać dowolną ilość razy. Jednak niezależnie od tego, dla optymalizatora kosztowego będzie to to samo zapytanie. Będzie mógł więc wykorzystać wcześniejszy plan wykonania. Na tym polega trick. Po prostu zamiast produkować właściwie identyczne zapytania różniące się tylko jedną wartością, korzystamy ze zmiennej bindowanej i wykonujemy zapytanie z jej użyciem. Podmieniamy tylko wartość tej zmiennej. Zmienne bindowane a CURSOR_SHARING 80/119

Możesz ustawić parametr cusor_sharing na force: ALTER SESSION SET CURSOR_SHARING=FORCE; ALTER SYSTEM SET CURSOR_SHARING=FORCE; dzięki czemu w przypadku takich drobnych różnic literalnych (jak employee_id w poprzednich przykładach), Oracle sam sobie stworzy zmienną bindowaną na potrzeby tej wartości i będzie działał tak, jakbyśmy stworzyli zmienną bindowaną i podstawiali jej wartości. W przypadku ustawienia CURSOR_SHARING na SIMILAR i jednoczesnym korzystaniu ze zmiennych bindowanych, Oracle będzie traktował tak samo dwa zapytania o takiej budowie: SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID=:x; ponieważ employee_id jest kolumną na którą jest założony klucz główny. Jakakolwiek wartość trafi do zmiennej bindowanej X, sens zapytania i sposób wykonania się nie zmienią. Zawsze uzyskamy jeden wiersz. W przypadku takiego zapytania: SELECT * FROM EMPLOYEES WHERE DEPARTMENT_ID=:x; sens zapytania w zależności od wartości X może ulec zmianie. Może być wiele wierszy które mają daną wartość w kolumnie department_id, a ich liczebność oraz rozłożenie może ulec zmianie w zależności od wartości x. 81/119

SQL Tuning Advisor W miejsce ręcznej optymalizacji zapytań SQL, można zastosować rozwiązania automatycznej optymalizacji dostarczane przez Oracle. Jednym z takich narzędzi jest SQL Tuning Advisor wprowadzony w Oracle 10g. Optymalizator kosztowy może działać w dwóch trybach: Normal mode Tuning mode Normal Mode Podczas zwykłego funkcjonowania, optymalizator kosztowy generuje dla zapytań plany wykonania. W tym trybie optymalizator ma narzucone ścisłe ograniczenia czasowe na działanie, zazwyczaj są to ułamki sekund. W tym czasie musi stworzyć jak najlepszy plan wykonania zapytania. Tuning Mode W trybie strojenia optymalizator wykonuje dodatkową analizę mającą na celu sprawdzenie czy plan wykonania zapytania wygenerowany w zwykłym trybie funkcjonowania można jeszcze ulepszyć. Wynikiem działania takiej dodatkowej analizy nie jest plan wykonania zapytania, a szereg proponowanych działań wraz z ich uzasadnieniem i szacowanymi korzyściami. Wywołany w trybie strojenia optymalizator nazywamy ATO (Automatic Tuning Optimizer). W trybie strojenia optymalizator może poświęcić nawet kilka minut na optymalizację zapytania. Ponieważ taka analiza również pochłania zasoby systemowe, ATO jest przeznaczony do wykorzystania dla złożonych i bardzo obciążających zapytań mających duże znaczenie dla całego systemu. Tryb strojenia jest włączany przez użytkownika dla wybranych przez niego zapytań. Automatic Database Diagnostic Monitor (ADDM) Poczynając od Oracle 10g, identyfikacją najbardziej obciążających system zapytań zajmuje się ADDM. Najbardziej obciążające zapytania to te zużywające najwięcej zasobów CPU, I/O i przestrzeni tymczasowej. Takie obciążające zapytania są dobrymi kandydatami do optymalizacji przez SQL Tuning Advisor. Otrzymuje on na wejściu zapytania wskazane przez ADDM i tworzy dla nich wskazówki optymalizacyjne wraz z uzasadnieniem, szacunkowymi korzyściami płynącymi z ich zastosowania i jeśli jest dostępna komendą która wdroży daną wskazówkę (np. komendę tworzącą odpowiedni indeks). Użytkownik może akceptować wskazówki Advisor'a i w ten sposób optymalizować zapytania. 82/119

Sposób działania SQL Tuning Advisor'a SQL Tuning Advisor jest jedynie kontrolerem procesu optymalizacji. Wywołuje on ATO (Optymalizator kosztowy działający w trybie strojenia) aby ten przeprowadził poniższe analizy: Analiza statystyk. ATO sprawdza obiekty biorące udział w zapytaniu pod kątem aktualności statystyk. Jeśli statystyki nie są aktualne, tworzy wskazówkę dotyczącą ich odświeżenia. Wykorzystując próbkowanie zbiera również dodatkowe informacje w celu dostarczenia brakujących lub niepoprawnych statystyk na wypadek gdyby wskazówki Advisor'a nie zostały zaimplementowane. Profilowanie SQL. Profil dla zapytania SQL jest tym, czym statystyki dla tabel. Bazując na kolejnych wykonaniach danego zapytania, ATO zbiera informacje na temat sposobu wykonania, i dzięki temu może lepiej dobrać podpowiedzi dla niego. Weryfikuje czy jego własne szacunki dotyczące wykonania zapytania pokrywają się z rzeczywistością. Profile SQL są formą indywidualnych dla zapytania informacji, statystyk i ustawień optymalizatora. ATO buduje profil zapytania SQL i sugeruje utworzenie go dla danego zapytania. Kiedy profil zostanie stworzony, będzie stosowany przez optymalizator kosztowy do tworzenia efektywniejszych planów wykonania danego zapytania w normalnym trybie działania optymalizatora. Dla użytkownika fakt korzystania z profilu SQL jest niezauważalny. Podczas budowania profilu, ATO testuje swoje własne wskazówki poprzez szacowanie obciążenia z nowymi ustawieniami, ale też częściowe realne wykonanie zapytania z użyciem nowych konfiguracji. Analiza ścieżek dostępu. ATO analizuje ścieżki dostępu w zapytaniu, i jeśli stworzenie indeksu poprawiłoby wydajność zapytania, rekomenduje jego utworzenie. Analiza struktury zapytania. Niekiedy sama struktura zapytania powoduje niską wydajność. W takich przypadkach ATO sugeruje zmianę zapytania. Wykrywa np. łączenia kartezjańskie, czy stosowanie UNION tam gdzie powinno się użyć UNION ALL. Źródła zapytań podlegających optymalizacji przez ATO Zapytania które będą analizowane i optymalizowane przez ATO mogą pochodzić z trzech źródeł: najbardziej obciążające zapytania zidentyfikowane przez ADDM zapytania SQL które znajdują się w cache'u wybrane zapytania podane przez użytkownika W przypadku zapytać pochodzących z cache, lub zakwalifikowanych przez ADDM, użytkownik ma możliwość wybrania niektórych zapytań. Nie ma konieczności optymalizacji wszystkich. 83/119

Sposób użycia SQL Tuning Advisor'a Aby przejść do STA, logujemy się do Enterprise Manager'a. Następnie klikamy link Advisor Central dostępny na dole strony: Następnie wybieramy SQL Advisors w górnej lewej części strony: 84/119

Dalej wybieramy SQL Tuning Advisor: Mamy tutaj możliwość wyboru źródła pochodzenia zapytań które mają podlegać optymalizacji: 85/119

Zapytania najbardziej obciążające Po wyborze Top Activity na poprzednim ekranie, zobaczymy wykres obciążenia bazy na przestrzeni czasu, pod kątem zużycia różnego rodzaju zasobów. W lewej dolnej części strony możemy wybrać spośród tych najbardziej obciążających, zapytania które chcemy optymalizować. Niekiedy tabela pozostaje pusta. Wystarczy wtedy odświeżyć stronę. Możemy podejrzeć treść zapytań klikając na link w kolumnie SQL ID: 86/119

Zaznaczamy je, upewniamy się że w selektorze Actions wybrane jest Schedule SQL Tuning Advisor i naciskamy Go : Pojawi nam się ekran który służy do uruchomienia analizy i optymalizacji wybranego zapytania. 87/119

Mamy możliwość wybrania typu analizy spośród dwóch opcji: Limited. Jeśli wybierzemy tę opcję, nie zostanie utworzony profil SQL, ale za to analiza przebiega bardzo szybko. Comprehensive. Wykonywana jest pełna analiza, tworzony jest też profil SQL dla zapytania. Taki rodzaj analizy trwa jednak znacznie dłużej niż w przypadku opcji Limited. Możemy też określić limit czasowy na analizę wszystkich wybranych do optymalizacji zapytań ( w oknie Total Time Limit ), oraz limit jednostkowy dla pojedynczego zapytania. Niżej, w sekcji Schedule możemy wybrać czy analiza ma się rozpocząć teraz, czy też chcemy ją odłożyć na nieco później (np. wtedy kiedy baza będzie mniej eksploatowana). Po zatwierdzeniu przyciskiem Submit (znajdującym się w prawym górnym rogu), zobaczymy taki oto ekran: Przez parę chwil, ATO będzie analizował nasze zapytania. Gdy skończy, zobaczymy listę proponowanych działań: 88/119

Dla niektórych podpowiedzi optymalizacyjnych będzie dodana opcja implementacji. O ile np. do podpowiedzi dotyczącej stworzenia indeksów czy odświeżenia statystyk ATO jest w stanie wygenerować komendę która od razu to wykona, o tyle do podpowiedzi np. dotyczących zmiany kształtu zapytania nie będzie to możliwe. W prawym górnym rogu mamy przycisk Show SQL, który pozwoli nam podejrzeć treść komendy którą ATO nam sugeruje: 89/119

Wybrałem jedną z podpowiedzi i kliknąłem Implement. System przeszedł do okna umożliwiającego zastosowanie wskazówki w tym przypadku odświeżenia statystyk tabel. Mamy tutaj też możliwość zastosowania wskazówki teraz, lub w innym terminie. 90/119

Przeprowadziłem jeszcze jeden test, tym razem wybierając nieco więcej zapytań. Proces analizy też trwał już dłużej. Po zakończeniu pojawił się ekran z podsumowaniem statystyk analizy. Wybieram przycisk Show all results : 91/119

Przejdziemy wtedy do ekranu z propozycjami ATO: 92/119

Analiza zapytań historycznych Przy użyciu ATO mamy też możliwość analizowania historycznych zapytań. Tym razem po przejściu do SQL Tuning Advisora link Historical SQL : Zobaczymy listę zapytań które nadal rezydują w cache kursorowym. Mamy tam informacje o zapytaniach jako takich, ale też dane na temat czasu wykonywania zapytania, ilość wykonań danego zapytania etc. 93/119

Podobnie jak wcześniej, zaznaczam zapytania które chcę poddać analizie, po czym wybieram przycisk schedule SQL Tuning Advisor : 94/119

Tak jak i wcześniej przechodzę przez widok w którym określam rodzaj analizy i moment jej przeprowadzenia: 95/119

Na kolejnym ekranie wybieram Show all results : i przechodzę do ekranu podpowiedzi, gdzie zaznaczam wybrane zapytanie i klikam View Recomendations aby zobaczyć podpowiedzi dla danego zapytania: Należy traktować podpowiedzi ATO troszkę z dystansem, ponieważ jest to tylko program, a program nie myśli w taki sposób jak człowiek. Nigdy też człowieka nie zastąpi. Nie traktujmy więc wszystkich podpowiedzi bezkrytycznie i weryfikujmy czy jego sugestie są racjonalne. Pamiętajmy też, że ATO nie wymyśli też wszystkiego co wymyślić może człowiek. Nie raz więc będzie się zdarzać że po ATO trzeba będzie poprawiać :) 96/119

Automatyzacja tuningu SQL Oracle 11g wprowadziło możliwość automatyzacji procesu tuningu. Specjalne przeznaczone do tego zadanie automatycznie przetwarza najbardziej obciążające zapytania, stosując SQL Tuning Advisor'a do ich analizy i wygenerowania profili SQL oraz podpowiedzi optymalizacyjnych. Profile SQL są implementowane automatycznie i nie wymaga to interwencji użytkownika. Inne metody np. zastosowanie indeksu, odświeżenie statystyk, poprawienie zapytania etc. użytkownik musi świadomie zatwierdzić. Taki automatyczny proces strojenia wykorzystuje zadanie AUTOTASK które domyślnie uruchamiane jest codziennie w nocy o 22. Tworzone profile SQL przed trwałą implementacją są testowane. Zapytania których dotyczą profile są wykonywane z użyciem profilu i bez niego, a następnie generowane obciążenia są ze sobą porównywane. Aby profil został zaimplementowany, suma CPU, czasu dostępów I/O musi być przynajmniej trzykrotnie mniejsza niż pierwotnie, przy czym żaden z tych parametrów nie może ulec pogorszeniu. Jeśli przy okazji analizy zostaną znalezione jakieś przestarzałe statystyki, ten fakt jest raportowany do zadania GATHER_STATS_JOB, dzięki czemu zostaną odświeżone przy kolejnym jego uruchomieniu. Profile SQL są wykorzystywane jeśli parametr CURSOR_SHARING jest ustawiony na EXACT. Konfiguracja atomatycznego tuningu Aby skonfigurować automatyczną analizę najbardziej obciążających zapytań, czy odświeżania przestarzałych statystyk, wybieramy w Enterprise Managerze zakładkę Server, a następnie automated maintance tasks : Dalej przy statusie w górnej części wybieramy przycisk Configure : 97/119

Zmieniamy global status na enabled. Możemy też osobno skonfigurować uruchamianie odświeżania statystyk, oraz tuningu SQL. 98/119

Przykładowo po wejściu w konfigurację statystyk, możemy ustawić np. po jakim czasie statystyki zostają uznane za przestarzałe Po wejściu w konfigurację generowania profili SQL możemy ustalić ich zakres, limit czasu wykonywania i ustalić czy automatycznie mają być implementowane, czy też jedynie raportowane: 99/119

Po ustawieniu wszystkich opcji możemy przejść do ustalenia kiedy zadania mają być wykonywane. Jeśli chcemy edytować istniejące okno zadania, klikamy po prostu jego nazwę np. Thursday_window. Proponuję sprawdzić strefę czasową wg której ustalany jest moment uruchomienia zadania, gdyż zadanie może się uruchomić w najmniej spodziewanym momencie :) Przykładowo na moim serwerze testowym (Oracle 11g, Oracle Red Hat Enterprise Linux 5), domyślnie ustawiona była strefa czasowa GMT -7. 100/119

Na potrzeby przykładu, nieco zmodyfikowałem okno czwartkowe: 101/119

Po włączeniu zadania : Zadanie w przypadku mojej bazy trwało kilka godzin. Rezultat po zakończeniu: 102/119

Zarządzanie planami SQL Czy zdarzyło Ci się kiedyś znaleźć się w sytuacji, kiedy wcześniej poprawna wydajność wykonywania zapytania uległa pogorszeniu? Najczęściej jest to spowodowane stosowaniem niepoprawnego planu wykonania zapytania. Może tak się zdarzyć np. w sytuacji odświeżenia statystyk dla obiektów biorących udział w zapytaniu, jeśli np. statystyki zostały odświeżone na pustych tabelach, a zapytania wykonywane są na wypełnionych. Zatrzymanie odświeżania statystyk i ich zablokowanie nie jest żadnym rozwiązaniem. Do tego typu sytuacji może zresztą dojść z wielu innych powodów. Stosowanie innych rozwiązań pośrednich, jest przynajmniej ryzykowne. Może pomóc w dłuższym lub krótszym czasie, ale może też zaszkodzić. Pewnym rozwiązaniem byłoby też zamrożenie planów wykonania przy użyciu OUTLINES, ale takie działanie uniemożliwi stosowanie lepszych planów jeśli takie w przyszłości się pojawią. W Oracle 11g wprowadzono nową funkcjonalność zarządzania planami zapytań SQL. Umożliwia ona przeglądanie i nadzorowanie ewolucji planów wykonania zapytań, a także sprawia że plany wykonania będą zmieniały się w sposób kontrolowany. Funkcjonalność ta domyślnie jest wyłączona, ale kiedy ją włączymy optymalizator przechowuje generowane przez siebie plany zapytań w specjalnym repozytorium. Tworzy się w ten sposób pewna forma historii planów wykonania dla zapytań. Optymalizator cały czas generuje nowy plany wykonania zapytań, ale wykorzystywał będzie jedynie te plany, które okazały się efektywniejsze niż wszystkie już znajdujące się w zbiorze. Dzięki takiemu sposobowi działania, plany wykonania mogą ewoluować tylko na lepsze. Taki zbiór różnych planów wykonania, można następnie przejrzeć, porównać ich wydajność. Możemy utrwalić wybrany plan dla danego zapytania (jak z użyciem OUTLINES), albo możemy zmienić jego status na ACCEPTED ręcznie. Status zmieni się na ACCEPTED również wtedy, gdy właśnie wygenerowany plan wykonania zapytania będzie lepszy od wszystkich dotychczas znalezionych. Zbiór takich zaakceptowanych planów nazywamy BASELINE. Optymalizator będzie wybierał plany wykonania tylko spośród tych zaakceptowanych. Nie zmienia to faktu że nowe plany wykonania są cały czas wymyślane, a jeśli okaże się że taki wymyślony plan jest lepszy od wszystkich znajdujących się w repozytorium to zostanie on dodany do zbioru planów spośród których wybiera optymalizator kosztowy. Domyślnie Oracle nie zbiera planów wykonania do repozytorium. Musimy takie zbieranie dopiero włączyć dla danej sesji. Służy do tego komenda: ALTER SESSION SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=TRUE; (Dłuższej nazwy parametru prawdopodobnie nie potrafili już wymyślić:) ). Wszystkie plany wykonania dla zapytań pojawiających się w ramach tej sesji będą teraz zbierane. Możemy też ustawić zbieranie planów dla całego systemu: ALTER SYSTEM SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=TRUE; 103/119

Po włączeniu zbierania statystyk, uruchamiam zapytanie z domyślnymi pierwotnymi konfiguracjami. Następnie zmieniam kilka warunków, co wpłynie na wykonywany plan. Po każdej takiej zmianie, wykonuję zapytanie ponownie. Plany wykonania zapytania które się nazbierały możemy przejrzeć w Enterprise Managerze. Aby to zrobić, wybieramy zakładkę Server, następnie klikamy link SQL Plan Control,następnie zakładkę SQL Baseline. Znajdziemy tutaj również możliwość konfiguracji zbierania planów, a także ich używania. W polu 104/119

Plan Retention (weeks) ustawiamy żywotność planu. Jeśli jakiś plan nie jest używany przez ilość tygodni określoną w tym oknie, to zostaje on automatycznie usunięty z repozytorium. Ilość tygodni możemy modyfikować. Wprowadzamy w pole ilość tygodni, a następnie zatwierdzamy zmianę klikając przycisk Configure. 105/119

Aby włączyć wykorzystywanie zebranych i zaakceptowanych planów wykonania zapytania posługujemy się komendą: ALTER SESSION SET OPTIMIZER_USE_SQL_PLAN_BASELINES=TRUE; dla sesji, lub dla systemu: ALTER SYSTEM SET OPTIMIZER_USE_SQL_PLAN_BASELINES=TRUE; Po czym poznać że BASELINE jest wykorzystywany? Włączyłem użycie tej funkcjonalności, wyświetliłem plan wykonania dla zapytania dla którego wcześniej zbierałem plany. Na dole planu widać podany identyfikator BASELINE. 106/119

Podpowiedzi optymalizatora kosztowego Podpowiedzi optymalizatora stosuje się kiedy chcemy wpłynąć na sposób działania optymalizatora kosztowego. Możemy mu np. kazać skorzystać z indeksu, lub tego zabronić. Możemy nakazać mu wykorzystanie określonego algorytmu łączenia tabel, a także wpływać na wiele innych procesów. Generalnie podpowiedzi (hintów) używa się w ostatniej kolejności w procesie optymalizacji i tylko wtedy gdy optymalizator kosztowy ewidentnie się myli, albo mamy do czynienia z sytuacjami o których optymalizator kosztowy nie wie (np. czas wykonania zapytania dla algorytmu o teoretycznie wyższym koszcie jest krótszy). Podpowiedzi powinny następować zaraz po klauzuli SELECT i mają konstrukcję następującą: SELECT /*+ podpowiedź */ LAST_NAME, FIRST_NAME FROM EMPLOYEES; Podpowiedź musi się znaleźć pomiędzy /*+ a */ i następować zaraz po SELECT. Jeśli popełnimy błąd (np. literówka) podpowiedź zostanie zignorowana i potraktowana jako komentarz. Nie możemy wymusić hintem czynności niemożliwych, takich jak np. zastosowanie indeksu który założony jest na kolumnę innej tabeli niż ta do której sięgamy. 107/119

INDEX SELECT /*+ index(e nazwaindeksu)*/ nazwakolumny from nazwatabeli e; Wymusi zastosowanie indeksu do pobierania wartości z kolumny (np. zamiast Full scana po tabeli). NO_INDEX SELECT /*+ no_index(e nazwaindeksu)*/ nazwakolumny from nazwatabeli e; Zabroni wykorzystania indeksu do pobrania wartości z kolumny. 108/119

ALL_ROWS Select /*+ALL_ROWS*/ Employee_Id From Employees e; Wymusi stosowanie takich algorytmów by optymalizacja była nastawiona na efektywny zwrot wszystkich wierszy. FIRST_ROWS Select /*+FIRST_ROWS(n)*/ Employee_Id From Employees e; Wymusi stosowanie takich algorytmów by optymalizacja była nastawiona na efektywny zwrot pierwszych n wierszy. FULL Select /*+FULL(e)*/ employee_id from employees e; Wymusi wykorzystanie pełnego skanu po tabeli przy pobieraniu danych. Jeśli w zapytaniu użyjemy aliasu dla tabeli na której chcemy wymusić FULL SCAN, to musimy później w hincie również użyć aliasu a nie pełnej nazwy tabeli. 109/119

USE_NL Select /*+use_nl(e d)*/ * From Employees e join departments d using(department_id); Wymusi wykorzystanie algorytmu nested loops (pętle zagdzieżdżone) do łączenia tabel. 110/119

NO_USE_NL Select /*+no_use_nl(e d)*/ * From Employees e join departments d using(department_id); Zabroni wykorzystania algorytmu nested loops (pętle zagdzieżdżone) do łączenia tabel. 111/119

USE_MERGE Select /*+use_merge(e d)*/ * From Employees e join departments d using(department_id); Wymusi wykorzystanie algorytmu sort merge join podczas łączenia tabel. 112/119

NO_USE_MERGE Select /*+no_use_merge(e d)*/ * From Employees e join departments d using(department_id); Zabroni wykorzystania algorytmu sort merge join podczas łączenia tabel. 113/119

USE_HASH Select /*+use_hash(e d)*/ * From Employees e join departments d using(department_id); Wymusi wykorzystanie algorytmu hashującego do łączenia tabel. 114/119

NO_USE_HASH Select /*+no_use_hash(e d)*/ * From Employees e join departments d using(department_id); Zabroni wykorzystania algorytmu hashującego do łączenia tabel. CURSOR_SHARING_EXACT Select /*+cursor_sharing_exact*/ * from employees where employee_id=:x Wymusi potraktowanie zmiennych bindowanych w zapytaniu tak jakbyśmy mięli włączony parametr CURSOR_SHARING na EXACT w sytuacji kiedy mamy ustawiony inaczej niż domyślnie. 115/119

RESULT_CACHE Uwaga: funkcjonalność ta jest dostępna dopiero od Oracle 11g!!! Moim zdaniem jedna z lepszych funkcjonalności dodanych w tej wersji. Jeśli często zadajemy zapytania które zwracają nam ten sam wynik, ponieważ dane na podstawie których ten wynik jest wyliczany rzadko ulegają zmianom, możemy przy użyciu bufora RESULT CACHE wykorzystać wcześniejszy wynik. Działa to w taki sposób, że wynik zapytania jest zapisywany do bufora, a jeśli ponownie zażądamy wyniku tego samego zapytania, wynik zostanie pobrany z RESULT CACHE. Domyślnie jednak funkcjonalność ta nie jest wykorzystywana. Aby ją wykorzystać, musimy posłużyć się hintem optymalizatora kosztowego, lub ustawić taką opcję jako domyślną dla sesji/systemu. Jeśli ulegną zmianie dane na podstawie których został wyliczony wynik znajdujący się w RESULT CACHE, wynik taki zostaje oznaczony jako nieaktualny i zostanie przy kolejnym wywołaniu przeliczony ponownie. Zwykły explain plan bez użycia RESULT_CACHE: Plan z wykorzystaniem hinta: 116/119

Korzystając z pakietu dbms_result_cache możemy sprawdzić użycie bufora. Musimy jednak włączyć output, ponieważ to na niego zostanie wyrzucony raport: Zamiast korzystać z hinta, możemy ustawić korzystanie z result seta jako domyślne dla systemu. Poniżej dwie opcje: Opcja z FORCE ustawi korzystanie z result seta jako domyślne. To oznacza że wynik każdego zapytanie wyląduje w RESULT_SET. Czy jest to dobre rozwiązanie? Myślę że każdy powinien rozważyć to dla swojego przypadku. Należy pamiętać że w buforze obowiązuje kolejka LRU. W przypadku dużej rotacji, może się okazać że wynik zapytania jest z bufora wypychany zanim zostanie ponownie użyty, natomiast przez RESULT_SET przelatują wyniki rzadko zadawanych zapytań, które zapychają nam bufor. Opcja MANUAL przywróci stan domyślny tj. aby wynik zapytania wylądował w RESULT SETcie trzeba będzie użyć hinta. 117/119

Istnieje też możliwość ustawienia domyślnego korzystania z RESULT SETa dla pojedynczej sesji: WYNIKI ZNAJDUJĄCE SIĘ W BUFORZE RESULT SET SĄ DOSTĘPNE POMIĘDZY SESJAMI (to akurat raczej dobra wiadomość :) )! RESULT SET możemy również wykorzystywać w PL SQL. Rozchodzi się o tą linijkę : "result_cache relies_on(employees)". Taki dopisek sprawi, że wyniki pochodzące z wywołania tej funkcji będą cache'owane w RESULT SETcie. Wynik ten zostanie wykorzystany przy ponownym wywołaniu tej funckji dla takich samych wartości parametrów. 118/119

Przetestujmy więc działanie tego: Wywołałem funkcję którą zdeklarowałem wcześniej tak by jej wyniki były domyślnie cachowanie w RESULT SETcie i sprawdzam bufor: Elegancko się zbuforowało :) Podpowiedzi optymalizatora jest znacznie więcej. Tutaj przedstawiłem tylko te najczęściej używane. 119/119