Zanim przejdziemy do prezentacji dalszych reguł transformacji, wprowadzimy obecnie przykładową bazę danych, do której będziemy się odwoływać w dalszej części wykładu. baza danych składa się z dwóch relacji: Wykładowca i Wykład. Kluczami relacji są atrybuty podkreślone linią ciągłą. Na slajdzie podane są rozmiary obu relacji.
Do kwestii opłacalności mechanizmu przepisywania zapytań (transformacji zapytań) wrócimy jeszcze w dalszej części wykładu.
Niemniej, technika transformacji i przepisywania zapytań jest ważnym elementem procesu optymalizacji zapytań. Jest ona szczególnie istotna dla dużych złożonych zapytań, dla których optymalizacja kosztowa, o której powiemy za chwilę, jest zbyt „kosztowna” i , najczęściej, mało efektywna.
W poprzednich latach, proces optymalizacji zapytań ograniczał się do optymalizacji regułowej, nazywanej czasami syntaktyczną. Optymalizacja regułowa okazała się mało skuteczna. W późniejszym okresie, optymalizacja regułowa została uzupełniona o mechanizm optymalizacji kosztowej. Należy jeszcze wspomnieć, w odniesieniu do optymalizacji regułowej, o problemie wyboru reguł. Dane jest zapytanie i zbiór reguł transformacji, który można zastosować w stosunku do zapytania. Jakie reguły zastosować?
Kluczowe znacznie ma w przypadku optymalizacji kosztowej oszacowanie kosztu wykonania danego planu. Dla każdego planu wykonania zapytania optymalizator musi oszacować:
–Szacunkowy koszt wykonania każdego operatora w planie zapytania (zależny od rozmiarów argumentów wejściowych operatora)
–Szacunkowy rozmiar wyniku wykonania każdego operatora w planie zapytania (dane znajdują się w katalogu bazy danych, dla operacji selekcji i projekcji zakładamy niezależność atrybutów).
Katalog bazy danych zawiera informacje o:
– liczbie krotek każdej relacji R (card(R)) i liczbie stron (Pcard(R)) dla każdej relacji, czasami katalog przechowuje również informacje o liczbie różnych wartości każdego atrybutu relacji (val(A[R]),
– liczbie różnych wartości atrybutu (NKeys) i liczbie stron dla każdego indeksu,
– wysokości indeksu, minimalnej i maksymalnej wartości klucza indeksu (Low/High).
Katalog jest odświeżany periodycznie. Bieżąca, ciągła, aktualizacja katalogu, w przypadku każdej zmiany parametrów systemu, jest zbyt kosztowna. W ostatnim czasie, coraz częściej, systemy zarządzania bazami danych przechowują histogramy dla atrybutów relacji.
rozmiar wyniku jest równy maksymalnej liczbie krotek * iloczyn wszystkich współczynników selektywności związanych z poszczególnymi predykatami selekcji. Podane powyżej oszacowanie wyniku zapytania zakłada, że predykaty są niezależne, co najczęściej nie jest spełnione w praktyce, ale istotnie ułatwia oszacowanie wyniku.
W jaki sposób obliczana jest wartość współczynnika selektywności dla danego predykatu?
Wartości współczynnika selektywności, dla poszczególnych typów predykatów przedstawiono na slajdzie. Przykładowo, jeżeli predykat term posiada postać „atrybut = wartość”, wówczas wartość współczynnika selektywności wynosi 1/val(atrybut), gdzie val(atrybut) oznacza liczbę różnych wartości danego atrybutu. Załóżmy, że predykat posiada postać „miasto = ‘Konin’ i val(miasto) = 5 (atrybut miasto przyjmuje 5 różnych wartości). Wówczas, szacunkowa liczba krotek spełniających dany predykat wynosi 1/5 liczby krotek. Przykładowo, jeżeli dana relacja Pracownicy posiada 1000 krotek i zapytanie do relacji jest sformułowane następująco:
Select nazwisko
From Pracownicy
Where miasto=’Konin’
Wówczas optymalizator szacuje, że w wyniku zapytania otrzymamy 1/5 * 1000 = 200 krotek.
Oczywiście, ze względu na rozkład wartości atrybutu miasto , podane oszacowanie może się istotnie różnić od rzeczywistego rozmiaru wyniku.
Niech S oznacza wynik wykonania operacji selekcji na relacji R. Niech card oznacza rozmiar relacji. Wówczas, dla prostego predykatu selekcji (Atrybut = wartość),
sf definiujemy następująco:
sf = 1/val(A[R ])
Przy założeniu równomiernego rozkładu wartości krotek:
card(S ) = sf * card(R )
(patrz przykład relacji Pracownicy przedstawiony na poprzednim slajdzie).
Zauważmy, że selekcja nie wpływa na szerokość wynikowej relacji, stąd
size(S ) = size(R )
(gdzie size(R) oznacza szerokość relacji R).
Rozważmy atrybut B, który nie uczestniczy w warunku selekcji. Określenie wartości val(B[S]) jest następujące: dane n=card(R) (zakładamy, że krotki relacji R są równomiernie rozłożone pomiędzy m = val(B[R]) kolorów). Ile różnych kolorów c= val(B[S]) wybierzemy, jeżeli losowo wybierzemy r krotek z relacji R?
Select nazwisko, stanowisko
From Pracownicy
Where miasto=’Konin’
Dane: n=1000 (krotek relacji Pracownicy), m=10 (różnych stanowisk), r =200 = card(S) (rozmiar wyniku zapytania). Stąd, zgodnie z aproksymacją Yao, val(stanowisko[S]) = m = 10.
- Projekcja na pojedynczym atrybucie A: rozmiar wyniku zapytania S (card(S) = val(A[R])) jest równy liczbie różnych wartości atrybutu A w relacji R.
- Jeżeli iloczyn różnych wartości atrybutów wyspecyfikowanych w wyniku projekcji jest mniejszy niż card(R), wówczas card(S) = iloczyn różnych wartości atrybutów wyspecyfikowanych w wyniku projekcji.
- Jeżeli projekcja zawiera klucz relacji R, wówczas card(S) = card(R).
Jeżeli założymy, że operator projekcji nie usuwa duplikatów z wynikowej relacji, to oszacowanie rozmiaru relacji S wynosi:
card(S) = card(R)
Szerokość wyniku projekcji jest równa sumie rozmiarów atrybutów wyspecyfikowanych w projekcji. Liczba różnych wartości atrybutów należących do wyniku projekcji S jest identyczna z liczbą różnych wartości tych samych atrybutów w relacji R.
Dla wszystkich atrybutów A należących do G: size(R.A) = size (S.A).
Podobnie, dla wszystkich atrybutów A należących do G: val(A[S]) = val(A[R]).
Górne ograniczenie rozmiaru relacji T wynosi: card(T) <= card(R) + card(S). Równość zachodzi w przypadku, gdy nie eliminujemy duplikatów. Szerokość relacji T wynosi: size(T) = size(R) = size(S).
Górna granica liczby różnych wartości atrybutu A w relacji T wynosi: val(A[T]) < val(A[R]) + val(A[S]).
Oszacowanie rozmiaru relacji T, będącej wynikiem połączenia relacji R i S, jest bardzo trudne. Można łatwo podać górne ograniczenie rozmiaru relacji T, które wynosi: card(T) < card(R) * card(S), ale jest to bardzo duże przybliżenie. Najczęściej podaje się następujące przybliżenie rozmiaru relacji T:
card(T) = (card(R) * card(S))/max{val(A[R]), val(A[S])}, gdzie A oznacza atrybut połączeniowy relacji R i S.
W przypadku połączenia naturalnego, od szerokości wyniku odejmujemy szerokość atrybutu połączeniowego.
Niech A oznacza atrybut połączeniowy, wówczas górne ograniczenie liczby różnych wartości atrybutu A wynosi: val(A[T]) < min(val(A[R]), val(B[S])).
Jeżeli A nie jest atrybutem połączeniowym, wówczas liczba różnych wartości atrybutu A wynosi: val(A[T]) < val(A[R]) + val(B[S]).
Histogramy umożliwiają uzyskanie znacznie dokładniejszych oszacowań rozmiarów wyników wykonania operacji. Histogramy występują w różnych wersjach:
- Histogram typu equi-depth (o równej głębokości) oznacza histogram, w którym dla każdego pojedynczego interwalu histogramu występuje równa liczba wartości atrybutu;
- Histogram typu equi-width (o równej szerokości) oznacza histogram, w którym wszystkie interwały posiadają równą szerokość.
Ze względu na znaczenie operacji połączenia w systemach relacyjnych baz danych i trudności w oszacowaniu rozmiaru tej operacji, histogramy są szczególnie przydatne do oszacowania wyniku operacji połączenia.
Oszacowanie rozmiaru wyniku wykonania operacji połączenia relacji Wykładowca i Etaty , z wykorzystaniem przedstawionych wcześniej histogramów, daje 72 krotki. Zauważmy, że błąd oszacowania poprzednią metodą jest ponad 400 razy większy! Ten przykład ilustruje przydatność histogramów do szacowania wyniku wykonania operacji połączenia.
W większości komercyjnych systemów zarządzania bazami danych, stosuje się tak zwany model operatorowy, w którym operacje leżące na pojedynczej ścieżce planu wykonania zapytania są wykonywane łącznie. Ma to na celu umożliwienie przetwarzania potokowego krotek (np., jeżeli wykorzystujemy indeks w celu selekcji krotek relacji, operacja projekcji jest wykonywana dla każdej wybranej krotki, która następnie przesyłana jest do operatora agregacji). W konsekwencji nie zachodzi potrzeba materializowania (tj. zapisywania na dysk do osobnego pliku temporalnego) częściowych wyników wykonania operacji danego planu.
Optymalizator rozpoczyna analizę możliwych ścieżek dostępu do relacji Wykładowca . Jeżeli w systemie został zdefiniowany indeks (I) na atrybucie stanowisko relacji Wykładowca , to wówczas jedną z możliwych ścieżek dostępu do tej relacji jest dostęp poprzez zdefiniowany indeks. Oszacowanie kosztu dostępu do relacji za pomocą indeksu zależy od typu indeksu. Jeżeli jest to indeks gęsty, wówczas, zgodnie ze wzorem przedstawionym na slajdzie 11, wynikowy rozmiar selekcji wynosi 1/10 * 40000 (liczba krotek relacji Wykładowca ) = 4000 krotek. Liczba operacji odczytu stron bazy danych zależy od typu indeksu – czy jest to indeks zgrupowany czy tez indeks nie zgrupowany. Oszacowanie liczby odczytywanych stron, dla tych dwóch typów indeksów, przedstawiono na slajdzie. W przypadku, gdy w systemie został zdefiniowany indeks (I) na kluczu relacji Wykładowca (sid), ale brak indeksu na atrybucie stanowisko , wówczas należy odczytać wszystkie strony indeksu i relacji. Liczba odczytanych stron wynosi wówczas 550 stron. W przypadku, gdy nie dysponujemy indeksem na relacji Wykładowca , pozostaje operacja skanowani relacji, której koszt wynosi 500 stron. Po przeanalizowaniu wszystkich możliwych ścieżek dostępu, optymalizator wybiera ścieżkę o „najmniejszym” koszcie.
Istnieje wiele struktur drzew połączeń. Większość komercyjnych systemów baz danych dopuszcza tylko niektóre z nich. Krótko przedstawimy możliwe i stosowane drzewa połączeń. Podstawowym typem drzew połączeń, stosowanym przez większość optymalizatorów zapytań, są tak zwane drzewa lewostronnie zagnieżdżone (ang. left deep join tree). Cechą charakterystyczną tych drzew jest to, że sekwencja operacji połączenia jest wykonywana w sposób potokowy. Relacje (lub wyniki wykonania bloków zapytania) są zmaterializowane (relacje R3, R1, R5, R2, R4). Po połączeniu relacji R3 z R1, krotki tego połączenia mogą być przetwarzane potokowo – są one łączone z krotkami relacji, będącymi prawymi argumentami operacji połączenia. Zauważmy, że krotki otrzymywane w wyniku połączenia nie muszą być materializowane! Są one natychmiast konsumowane przez następny operator połączenia. Co więcej, w procesie łączenia relacji można wykorzystać indeksy na relacjach będących prawymi argumentami operacji połączenia. Większość systemów zarządzania bazami danych opuszcza tylko i wyłącznie drzewa tego typu. Dlaczego? Liczba możliwych drzew połączeń dla n relacji może być bardzo duża, natomiast liczba drzew lewostronnie zagnieżdżonych jest znacznie mniejsza. W przypadku n relacji istnieje tylko jeden kształt drzewa zagnieżdżonego, do którego można dopasować relacje na n silnia sposobów.
Z tego punktu widzenia, można rozpatrywać działanie optymalizatora zapytań w trzech aspektach:
- Modelu planu wykonywania zapytania (struktura drzewa połączeń),
- Algorytmu przeszukiwania przestrzeni stanów,
- Funkcji kosztów.
Jak już wspominaliśmy, większość komercyjnych systemów zarządzania bazami danych ogranicza się do analizy drzew lewostronnie zagnieżdżonych. Mimo przyjęcia określonego kształtu drzewa połączeń, nadal liczba możliwych drzew, które należy przeanalizować, i których koszt należy oszacować, może być bardzo duża. Pozostaje zatem problem wyboru strategii przeszukiwania przestrzeni możliwych drzew połączeń. Funkcja kosztu, stosowana przez optymalizatory, opiera się na oszacowaniach rozmiarów wyników operatorów, które przedstawiliśmy ma poprzednich slajdach.
- algorytmy dokładne,
- algorytmy przybliżone,
- metaheurystyki - algorytmy kombinatoryczne.
Algorytmy dokładne, stosowane, w praktyce, analizują wszystkie możliwe drzewa połączeń (tzw. pełne przeszukiwanie) lub ograniczają się do przejrzenia tylko niektórych podzbiorów. Najpopularniejszym algorytmem, stosowanym przez większość systemów komercyjnych, jest algorytm programowania dynamicznego. Koncepcja programowania dynamicznego polega na wypełnieniu tabeli kosztów wykonania połączeń tak, aby przechowywać tylko minimum danych potrzebnych do analizy. Tabela ta jest konstruowana, kolejno, dla pojedynczych relacji, par relacji, trojek relacji, itd. Wadą tych algorytmów jest zależność czasu optymalizacji od liczby operacji połączenia.
Algorytmy przybliżone, do znalezienia „najlepszego” planu stosują bądź heurystyki lub ograniczają się do optymalizacji regułowej, o której już mówiliśmy. Zasadniczą wadą tych algorytmów jest to, że, najczęściej, plan znaleziony przez algorytm przybliżony znacząco odbiega od „najlepszego” możliwego planu wykonania zapytania.
Trzecie z możliwych podejść do znajdowania najlepszego drzewa połączeń polega na zastosowaniu algorytmów kombinatorycznych takich jak: Iterative Improvement, Simulated Annealing, Tabu Search, algorytmy genetyczne. Algorytmy te pozwalają na znaczną penetrację przestrzeni możliwych drzew połączeń, a jednocześnie, czas optymalizacji nie zależy od liczby operacji połączenia. Jak pokazują badania, algorytmy, mimo, że nie znajdują optymalnych drzew połączeń, znajdują jednak plany znacznie efektywniejsze niż w przypadku stosowania prostych heurystyk. Jednak ich zasadniczą zaletą, w stosunku do algorytmów dokładnych, jest to, że pozwalają one optymalizować nawet bardzo duże zapytania ( o liczbie połączeń dochodzących do 100 operacji).
Problem optymalizacji zapytań jest ciągle obszarem aktywnych badań naukowych. Wiąże się to, z jednej strony, z problemem coraz bardziej złożonych zapytań i nowych typów danych, z drugiej, z nowymi potrzebami wynikającymi z popularności Internetu.