Poniższe rozważania dotyczą platformy SQL Server 2005, jednak spora ich część pośrednio lub bezpośrednio odnosi się także do wersji 2000.
Operacje typu join to nieodzowny element większości zapytań, nie bez znaczenia jest więc umiejętność spojrzenia na joiny w aspekcie wydajności. O ile wydajnością zapytania
ad hoc najczęściej możemy się nie przejmować, o tyle zapytanie zaszyte w ciele produkcyjnej procedury składowanej, wykonywane dziennie setki razy - jest już chyba godnym obiektem obserwacji.
Z logicznego punktu widzenia poszczególne joiny budowane są z 1-3 następujących po sobie faz:
CROSS JOIN
1. iloczyn kartezjański
INNER JOIN
1. iloczyn kartezjański
2. warunek filtrujący ON
OUTER JOIN
1. iloczyn kartezjański
2. warunek filtrujący ON
3. dodanie wierszy z oznaczonej tabeli
Powyższe rozróżnienie ma kilka ważnych konsekwencji. Jedną z nich jest fakt, że w INNER JOINach nie jest istotne, czy określony warunek zawężający umieścimy w INNER JOIN ON ... - czy w WHERE ..., gdyż ten typ joina składa się z dokładnie dwóch operacji logicznych i między zastosowaniem warunku ON i WHERE żadne wiersze nie są już do wynikowego zbioru dodawane. Efektem powyższego jest ponadto fakt, że umiejscowienie warunku nie wpływa na wydajność zapytania (w 99% przypadków uzyskamy te same plany wykonań). Oczywiście zupełnie inaczej zachowują się OUTER JOINy, gdzie ze względu na ostatnią fazę (dodanie wierszy z oznaczonej tabeli), warunek WHERE stosowany jest w stosunku do innego zbioru rekordów niż warunek ON.
Jeżeli chodzi o aspekt fizyczny budowania złączeń, to odbywa się to poprzez 3 algorytmy:
-
loop join
-
merge join
-
hash join
Strategie joinowania można wymuszać, np.:
SELECT kolumna
FROM tabela_a a
INNER LOOP JOIN tabela_b b
ON (..warunek..)
Nie jest to jednak zalecana metoda walki z niewydajnymi planami zapytań - trzeba raczej doprowadzić do sytuacji, w której sam optimizer dobiera odpowiedni sposób realizacji kwerendy (indeksy, aktualne statystyki, query tuning).
Spośród wymienionych strategii złączeń najefektywniejsze są merge joiny. Loop join może w danej sytuacji również być najlepszym algorytmem, natomiast występowanie w planach wykonań operacji typu hash join prawie zawsze oznacza brak jakiegoś indeksu lub nieaktualną statystykę.
W fazie tworzenia zapytania deweloperzy często ograniczają się do stosowania joinów wszędzie tam, gdzie jest to możliwe. Tymczasem join nie zawsze jest najwydajniejszym rozwiązaniem, czasem zastosowanie klauzuli EXISTS daje lepsze rezultaty, załóżmy istnienie 2ch tabel:
CREATE TABLE zamowienia
(
nr_zamowienia int,
nazwa_zamowienia nvarchar(50)
)
CREATE TABLE miejsca_zamowien
(
nr_zamowienia int,
miejscowosc nvarchar(50)
)
Szukane zapytanie ma wyświetlić miejscowości, w których nie było zamówień. Rozwiązanie z joinem wygląda tak:
SELECT mz.miejscowosc
FROM miejsca_zamowien mz
LEFT OUTER JOIN zamowienia z
ON (mz.nr_zamowienia = z.nr_zamowienia)
WHERE z.nr_zamowienia IS NULL
Zakładając przeciętne uwarunkowania środowiska zadania, zapytanie można często zakodować w efektywniejszy sposób, tj.:
SELECT miejscowosc
FROM miejsca_zamowien mz
WHERE NOT EXISTS
(SELECT * FROM zamowienia WHERE nr_zamowienia = mz.nr_zamowienia)
Jeżeli chodzi o kolejność występowania złączeń w zapytaniu, to o ile nie następuje zmiana logiki zapytania - z punktu widzenia wydajności kolejność jest nieistotna. Optimizer sam potrafi dobrać najoptymalniejszy układ joinowania tabel.
Więcej informacji: Itzik Ben-Gan, Lubor Kollar, Dejan Sarka,
Inside Microsoft® SQL Server™ 2005 T-SQL Querying (Chapter 5), MS Press 2006