Rozwiązywanie wąskich gardeł za pomocą indeksów i partycji SQL

Opublikowany: 2022-03-11

W pierwszej lekcji SQL Indexes Explained dowiedzieliśmy się, że zapytania SELECT są szybsze, gdy dane są już uporządkowane według wartości określonych kolumn.

W drugiej lekcji poznaliśmy podstawową strukturę indeksów B-drzewa i jak z nich korzystać, aby zmniejszyć ilość danych, do których uzyskujemy dostęp podczas wykonywania zapytania. Dowiedzieliśmy się również, jak zaimplementować zapytania łączące wiele tabel i jak indeksy mogą przyspieszyć takie zapytania.

Podkreśliliśmy również dwa scenariusze, w których pomocne jest użycie indeksów w SQL. Gdy indeksy obejmują indeksy zawierające wszystkie kolumny z zapytania — z warunków WHERE , warunków JOIN i listy SELECT — unikamy całkowicie czytania odpowiedniej tabeli. Alternatywnie, indeksy mogą pomóc, gdy zmniejszają liczbę bloków danych dostępnych do niewielkiej części rozmiaru tabeli.

W przeciwnym razie bardziej wydajne jest skanowanie całej tabeli niż czytanie z indeksu i losowe przeskakiwanie tam iz powrotem do odpowiednich wierszy tabeli.

Zapytania dotyczące zakresu SQL

Zapytania, które mogą korzystać z indeksów, zazwyczaj zawierają warunki, które znacznie zmniejszają zakres możliwych wartości, które może przyjąć jedna lub więcej kolumn. Zapytania o zakres ograniczają dane na podstawie warunków, takich jak „wartość kolumny A musi znajdować się między X a Y”.

Dobrym tego przykładem jest zapytanie z ćwiczenia 4 z drugiej lekcji:

 SELECT c.ClientName FROM Reservations r JOIN Clients c ON r.ClientID = c.ClientID WHERE r.DateFrom BETWEEN ( TO_DATE('2020-08-13', 'YYYY-MM-DD') AND TO_DATE('2020-08-14', 'YYYY-MM-DD') ) AND r.HotelID = 3;

Tutaj mamy dwa zakresy. Pierwszy z nich to zakres dat, okres od 13 sierpnia 2020 r. do 14 sierpnia 2020 r. Drugi to najmniejszy możliwy zakres liczbowy. Warunek jest równoznaczny z r.HotelID BETWEEN 3 AND 3 .

Ćwiczenie 1: Okresy (zapytania dotyczące zakresu dat i czasu)

Dodajmy do tabeli Reservations kolumnę o nazwie CheckInTime . W tym arkuszu kalkulacyjnym możesz zobaczyć przykładowe dane. Zwróć uwagę, że istnieje jeden indeks obejmujący zarówno CheckInTime , jak i ClientId .

Napisz zapytanie, które zwróci nazwy klientów, którzy zameldowali się 15 sierpnia 2020 r.

Niedoświadczeni programiści SQL zwykle piszą następujące zapytanie:

 SELECT c.ClientName FROM Reservations r JOIN Clients c ON r.ClientID = c.ClientID WHERE TO_DATE(r.CheckInTime, 'YYYY-MM-DD') = '2020-08-15';

Zakładają, że wykonanie zapytania wyglądałoby tak:

 Get first row from IX_CheckInTime_ClientID where TO_DATE(CheckInTime, 'YYYY-MM-DD') = '2020-08-15' While found and TO_DATE(CheckInTime, 'YYYY-MM-DD') = '2020-08-15' Fetch Clients.* where ClientID = IX_CheckInTime_ClientID.ClientID Write down Clients.ClientName Get next row from IX_CheckInTime_ClientID

Problem polega na tym, że w chwili pisania tego tekstu ani jeden RDBMS nie jest w stanie wygenerować takiego planu wykonania. Widzą TO_DATE (składnia Oracle) jako funkcję, która przekształca wartość kolumny CheckInTime na coś nieindeksowanego. Tak więc plany wykonania, które zwykle generują, wyglądają tak:

 For each row from IX_CheckInTime_ClientID If TO_DATE(CheckInTime, 'YYYY-MM-DD') = '2020-08-15' then Fetch Clients.* where ClientID = IX_CheckInTime_ClientID.ClientID Write down Clients.ClientName

Wykonanie tego byłoby szybsze niż odczytywanie wszystkich wierszy z tabeli Reservations , ponieważ wiersz indeksu jest węższy niż wiersz tabeli. Mniejszy rząd oznacza, że ​​z dysku trzeba będzie uzyskać dostęp do mniejszej liczby bloków.

Wiemy jednak, że pierwszy plan realizacji byłby znacznie skuteczniejszy. Aby przekonać nasz RDBMS do takiego podejścia, musimy przepisać zapytanie:

 SELECT c.ClientName FROM Reservations r JOIN Clients c ON r.ClientID = c.ClientID WHERE r.CheckInTime >= TO_DATE('2020-08-15 00:00:00', 'YYYY-MM-DD HH:MI:SS') AND r.CheckInTime < TO_DATE('2020-08-16 00:00:00', 'YYYY-MM-DD HH:MI:SS');

Jest to prawidłowe zapytanie zakresowe, które rozumie każdy dobry RDBMS. Nasz RDBMS dowiaduje się, że potrzebujemy danych z tabeli Reservations , w której wartość CheckInTime — a nie coś z niej wywodząca się — należy do dobrze zdefiniowanego zakresu. Generowany plan wykonania byłby bardziej podobny do:

 Get first row from IX_CheckInTime_ClientID where CheckInTime >= '2020-08-15 00:00:00' While found and CheckInTime < '2020-08-16 00:00:00' Fetch Clients.* where ClientID = IX_CheckInTime_ClientID.ClientID Write down Clients.ClientName Get next row from IX_CheckInTime_ClientID

Właśnie tego naprawdę chcemy: wykorzystać nie tylko sam indeks, ale także fakt, że jest posortowany.

Ćwiczenie 2: LIKE z dziką kartą na początku

Tym razem nasz detektyw przyjeżdża do hotelu z niejasną informacją o podejrzanym: tylko, że nazwisko kończy się na „-syn”. Detektyw chce podać imiona i nazwiska wszystkich takich gości:

 SELECT FirstName, LastName FROM Clients WHERE LastName LIKE '%son';

W przypadku tabeli Clients i indeksu LastName użyjemy tego arkusza kalkulacyjnego. Zapisz wyniki, które zwróci zapytanie. Pomyśl o różnych podejściach, które możesz zastosować.

Podejście do skanowania tabeli

Najprostszą strategią jest odczytanie wszystkich danych z tabeli i zapisanie imion gości, gdy ich nazwisko kończy się na „-son”:

 For each row from Clients If LastName like '%son' then write down FirstName, LastName

Tutaj musielibyśmy kolejno czytać całą tabelę.

Korzystanie z indeksu

Spróbujmy skorzystać z indeksu w kolumnie LastName . Przejdź do arkusza IX_LastName, użyj go, aby znaleźć wszystkich klientów spełniających podane kryterium i zapisz ich nazwiska.

Okazuje się, że trzeba przeczytać cały indeks, aby znaleźć wszystkich Andersonów, Robinsonów i Thompsona ze stołu. Czy to lepsze niż skanowanie tabeli? Oprócz odczytania całego indeksu musiałeś również znaleźć dla każdego pasującego wpisu odpowiedni wiersz z tabeli za pomocą wartości rowAddress , a następnie zapisać stamtąd FirstName :

 For each row from IX_LastName If LastName like '%son' then Fetch Clients.* where RowAddress = IX_LastName.RowAddress Write down FirstName, LastName

Dla nas prostsze i szybsze było sekwencyjne czytanie tabeli. W przypadku naszych RDBMS zależałoby to od procentu wierszy spełniających kryteria. Jeśli w dużej tabeli jest tylko garstka Andersonów, Robinsonów i Thompsons, RDBMS odczytałby mniej bloków danych ze znacznie węższych wpisów indeksu, nawet jeśli musiałby odczytać kilka bloków z tabeli po znalezieniu dopasowania. W przeciwnym razie skanowanie tabeli zajmuje mniej czasu.

Porządkowanie danych w indeksie nie pomaga w takim zapytaniu. Mniejszy rozmiar wiersza indeksu może być pomocny — ale tylko czasami.

Ćwiczenie 3: LIKE z dziką kartą na końcu

Następnym razem, gdy przyjdzie nasz detektyw, musimy znaleźć wszystkich klientów, których nazwiska zaczynają się od „Rob-”.

 SELECT FirstName, LastName FROM Clients WHERE LastName LIKE 'Rob%';

Spróbuj wyodrębnić dane pasujące do zapytania z tego samego arkusza kalkulacyjnego.

Jeśli korzystałeś ze skanowania tabel, straciłeś możliwość pełnego wykorzystania indeksu IX_LastName . Znacznie szybciej jest zlokalizować pierwszy wpis z indeksu zaczynający się od „Rob-” (Roberts), odczytać kolejne wiersze (zarówno Robertses, jak i Robinsons) i zatrzymać się, gdy LastName nie spełnia już kryterium:

 Get first row from IX_LastName where LastName <= 'Rob' While found and LastName < 'Roc' Fetch Clients.* where rowAddress = IX_LastName.rowAddress Write down FirstName, LastName Get next from IX_LastName

W tym przypadku, po przeszukaniu B-drzewa dla pierwszego wpisu, odczytujemy tylko wpisy spełniające kryterium. Przestajemy czytać, gdy tylko przeczytamy imię, które nie pasuje do kryterium.

Rozwiązywanie problemów związanych ze skalowaniem drzewa B

Zazwyczaj, gdy wdrażamy nową bazę danych, pojawia się kilka wypełnionych tabel przeglądowych i pustych tabel transakcyjnych. System działa płynnie od samego początku, zwłaszcza jeśli przestrzegaliśmy dobrych praktyk projektowania baz danych poprzez normalizowanie tabel; tworzenie kluczy podstawowych, obcych i unikalnych; i wspieranie kluczy obcych z odpowiednimi indeksami.

Po kilku miesiącach lub latach, kiedy ilość danych znacząco wzrosła złożoność systemu i bazy danych, zaczynamy zauważać pogorszenie wydajności. Pojawiają się opinie o tym, dlaczego system zwalnia i co z tym zrobić.

W powszechnej opinii często głównym winowajcą jest rozmiar bazy danych. Wydaje się, że rozwiązaniem jest usunięcie danych historycznych, których nie potrzebujemy na co dzień, i umieszczenie ich w osobnej bazie danych do raportowania i analiz.

Przyjrzyjmy się najpierw głównemu założeniu.

Zapytania o zakres SQL: czy czas wykonania zależy od rozmiaru tabeli?

Rozważ typowe zapytanie zakresowe z pojedynczej tabeli:

 SELECT Column1, …, ColumnN FROM Table WHERE Column BETWEEN X AND Y;

Zakładając, że istnieje indeks na Column , optymalny plan wykonania to:

 Get first row from IX_Column where Column between X and Y While found and Column <= Y Fetch Table.* where rowAddress = IX_Column.rowAddress Write down Column1, …, ColumnN Get next row from IX_Column

Policzmy bloki, które RDBMS będzie musiał odczytać, aby zwrócić te dane.

Część Get first row jest implementowana przez wyszukiwanie B-drzewa, które wprowadziliśmy w drugiej lekcji. Liczba bloków, które musi odczytać, jest równa głębokości B-drzewa. Następnie odczytujemy kolejne pozycje z poziomu liścia indeksu.

W przypadku zapytań OLTP zazwyczaj wszystkie wyniki znajdują się w jednym bloku indeksu (czasem dwóch, ale rzadko więcej). Oprócz tego dla każdego wpisu indeksu mamy dostęp do bloku w tabeli, aby znaleźć odpowiedni wiersz na podstawie jego adresu. Niektóre wiersze tabeli mogą znajdować się w tym samym bloku tabeli, który już załadowaliśmy, ale aby uprościć szacowanie, załóżmy, że za każdym razem ładujemy nowy blok.

Więc formuła to:

B = D + 1 + R

B to całkowita liczba odczytanych bloków, D to głębokość B-drzewa, a R to liczba wierszy zwróconych przez zapytanie.

Jedynym parametrem zależnym od liczby wierszy w tabeli jest D, głębokość B-drzewa.

Aby uprościć obliczenia i zwrócić uwagę, załóżmy, że w jednym bloku mieści się 1000 wpisów indeksu. D = 1, o ile w tabeli jest mniej niż 1000 wierszy. W przypadku tabel zawierających transakcje biznesowe może to mieć miejsce w pierwszym dniu roboczym po wdrożeniu systemu. Wkrótce głębokość B-drzewa wzrośnie. Dopóki w tabeli jest mniej niż 1 milion wierszy, indeks będzie składał się z dwóch poziomów.

Jeśli przeszkadzają nam powolne czasy odpowiedzi bazy danych i obwiniamy za to ilość danych, pamiętaj, że tabele transakcji często zawierają zaledwie miliony wierszy. Ponieważ tylko 1 milion wierszy pasuje do dwupoziomowego indeksu B-drzewa, głębokość musi wynosić co najmniej trzy. Głębokość nie wzrośnie do czterech, chyba że w tabeli będzie więcej niż 1 miliard wierszy. Teraz mamy dokładniejsze oszacowanie:

B = 4 + R

Jeśli R jest małe, obniżenie głębokości B-drzewa z powrotem do dwóch znacznie przyspieszyłoby zapytanie. Kiedy szukamy według podstawowej lub unikalnej wartości klucza, system odczyta cztery bloki zamiast pięciu, co stanowi 20% poprawę. Jeśli zapytanie zwróci więcej wierszy, poprawa może nie być zauważalna. Problem polega na tym, że w przypadku wielu aplikacji możemy nie być w stanie obsłużyć wymaganych operacji biznesowych, przechowując w bazie danych mniej niż 1 milion transakcji.

Wniosek wydaje się więc taki, że rozmiar stołu nie ma znaczenia; innymi słowy, przenoszenie danych historycznych to strata czasu i zasobów.

Ale nie tak szybko: dowiedzmy się więcej o strukturze indeksu B-drzewa i jak wpływają na niego zmiany danych.

Szczegóły implementacji indeksu B-drzewa

W naszym omówieniu indeksów B-drzewa w drugiej lekcji zobaczyliśmy, że wszystkie poziomy zrównoważonego drzewa są (fizycznie) uporządkowane według wartości kolumn kluczowych. Jednak gdy chcemy wstawić, zaktualizować lub usunąć element, często musimy przenieść dużą ilość danych, aby zachować kolejność.

Załóżmy, że wstawiamy w środku bloku, który jest pełny. Musimy podzielić blok, przearanżować dane, a czasem nawet zaktualizować dane na innym poziomie B-drzewa, wskazując na aktualny.

Aby takie przypadki były bardziej wydajne, każdy element indeksu zawiera wskaźniki do poprzedniego i następnego wiersza, dzięki czemu jest podwójnie połączony. W przypadku wstawiania ogólnie oznacza to, że po prostu piszemy nowe elementy jak najbliżej poprzedniego i poprawiamy wskaźniki.

Kiedy musimy również podzielić blok, musimy napisać nowy element na poprzednim poziomie B-drzewa. To tylko kwestia poprawienia kilku dodatkowych wskaźników — nie ma potrzeby przepisywania dużych części drzewa. Po podziale oba bloki danych są w przybliżeniu zapełnione do połowy. W zależności od tego, gdzie na dysku jest wolne miejsce, „sąsiadujące” bloki mogą być fizycznie dość odległe.

Po pewnym czasie zwiększa się fragmentacja indeksu i zauważalne staje się spowolnienie wykonywania zapytania. Z RDBMS wykonującym zapytania w sposób, w jaki je opisaliśmy, założenie kolejności i bliskości elementów staje się coraz mniej poprawne, co prowadzi do znacznie większej liczby odczytów. W najgorszym przypadku, gdy wszystkie bloki danych są w połowie puste, system musi odczytać dwa razy więcej bloków.

Utrzymanie indeksu B-drzewa

Lekarstwem na to jest defragmentacja indeksu (lub „ponowna indeksacja”). Każdy RDBMS zapewnia funkcję odtwarzania całego indeksu; po ponownej indeksacji indeksy są ponownie fizycznie uporządkowane.

Ponowne indeksowanie to dość szybka operacja, mimo że odczytuje i zapisuje duże ilości danych. Nowoczesne systemy RDBMS zazwyczaj oferują dwa tryby ponownego indeksowania, przy czym szybszy wymaga blokowania tabel podczas przetwarzania. Tak czy inaczej, lepiej jest ponownie indeksować poza godzinami szczytu. W przeciwnym razie przetwarzanie może spowolnić wydajność bazy danych.

Usuwanie danych historycznych

Gdy mamy tabele z miliardami, a nawet setkami milionów wierszy, zakończenie operacji ponownego indeksowania poza godzinami szczytu może być niewykonalne.

Aby uniknąć tej sytuacji, rozwiązaniem może być przeniesienie danych historycznych z bazy danych OLTP. Jeśli jednak po prostu usuniemy wiersze starsze niż określony próg, sprawimy, że indeksy będą jeszcze bardziej pofragmentowane i będziemy musieli je ponownie indeksować jeszcze częściej.

Partycjonowanie SQL na ratunek?

Istnieje sposób na uniknięcie fragmentacji spowodowanej usuwaniem danych historycznych, zachowując w produkcyjnej bazie danych tylko „aktywne” transakcje. Pomysł, który implementują wszystkie główne RDBMS, polega na podzieleniu tabeli na mniejsze porcje (zwane partycjami ) i zapewnieniu możliwości ich dodawania, usuwania, a nawet przełączania między tabelami (np. z aktywnej tabeli na historyczną z tymi samymi Struktura).

Rzućmy okiem na tabelę Reservations podzieloną na partycje w tym arkuszu kalkulacyjnym. Tabela jest podzielona według miesięcy, a nazwy partycji są odwzorowane na okresy dat i inne arkusze kalkulacyjne. Aby zobaczyć, jak wykonywane jest zapytanie w tabeli podzielonej na partycje, wykonamy kilka ćwiczeń.

Ćwiczenie 4: Zapytanie o partycje w SQL

Z arkusza kalkulacyjnego, do którego link znajduje się powyżej, spróbuj wyodrębnić dane wymagane przez następujące zapytanie — bez używania żadnych indeksów:

 SELECT HotelID, ReservationID, ClientID, DateFrom, DateTo FROM Reservations WHERE DateFrom BETWEEN TO_DATE('2021-03-01','YYYY-MM-DD') AND TO_DATE('2021-03-03');

Prawdopodobnie zorientowałeś się, że musisz najpierw spojrzeć na arkusz mapowania partycji i znaleźć partycję zawierającą rezerwacje z marca 2021 r. Następnie otworzyłeś odpowiednią partycję, sekwencyjnie odczytałeś dane i odfiltrowałeś wiersze, które nie spełniały wymagań stan: schorzenie.

Chociaż jest to proste, prawdopodobnie nie podobało ci się przechowywanie tak małej liczby wierszy po przeczytaniu tak wielu. Czytanie marcowego partycji było lepsze niż czytanie całej tabeli rezerwacji, ale nadal nie było idealne. A co z indeksami?

Indeksy globalne

Systemy RDBMS umożliwiają nam tworzenie globalnego indeksu obejmującego wszystkie partycje tabeli partycjonowanej. Ale nie ma różnicy między tym, jak działają indeksy globalne i zwykłe: indeksy globalne nie uwzględniają partycji. W związku z tym zapytania CRUD używające indeksu globalnego nie obejmują mapy partycji dla swojej tabeli.

Musimy zaktualizować mapę partycji tylko wtedy, gdy usuniemy całą partycję. Następnie musimy usunąć z indeksu wszystkie wiersze wskazujące na usuniętą partycję. Oznacza to, że cały globalny indeks wymaga przebudowy.

Okres przestoju pozostaje niezbędny, ponieważ indeksów nie można używać, dopóki nie zostaną usunięte przestarzałe elementy. Jeśli możemy regularnie usuwać partycje, ograniczając liczbę aktywnych, to operacja ponownego indeksowania może zmieścić się w oknie przestoju. Tak więc użycie partycji pomaga w pierwotnym problemie, skracając czas potrzebny na zadania konserwacyjne, w tym konserwację indeksów globalnych.

Ale co, jeśli nadal nie stać nas na awarię?

Indeksy podzielone globalnie

Ta strategia rozwiązuje ten problem: po prostu dzielimy indeks w taki sam sposób, jak dzielimy tabelę. W arkuszach kalkulacyjnych, z którymi łączy się arkusz kalkulacyjny partycji, każda partycja zawiera swoją część tabeli Reservations i arkusz indeksu o nazwie IX_DateFrom, oba podzielone według DateFrom .

Aby wykonać zapytanie z ćwiczenia 4, RDBMS najpierw spojrzy na mapę partycji indeksu i zidentyfikuje, które partycje zawierają daty z zakresu. (W naszym przypadku jest to tylko jedna partycja indeksu.) Następnie użyje wyszukiwania B-drzewa, przejdzie do poziomu liścia i na koniec uzyska dostęp do tabeli przy użyciu odpowiedniego adresu wiersza.

Kiedy usuniemy partycję z tabeli, wystarczy usunąć odpowiednią partycję z indeksu. Nie są konieczne żadne przestoje.

Lokalne indeksy

Główną wadą globalnie podzielonych indeksów jest to, że musimy zadbać o usunięcie zarówno tabeli, jak i odpowiadającej jej partycji indeksu. Istnieje tylko niewielki dodatkowy koszt związany z odczytem i utrzymaniem samej mapy partycji indeksu.

Indeksy lokalne obejmują podobne, ale nieco inne podejście. Zamiast partycjonować pojedynczy indeks globalny, tworzymy indeks lokalny wewnątrz każdej partycji tabeli. W ten sposób indeksy lokalne mają tę samą główną zaletę, co indeksy z podziałem globalnym — tj. brak przestojów — przy jednoczesnym unikaniu ich wad.

Wydaje się, że to idealne rozwiązanie. Ale zanim zaczniemy świętować, przyjrzyjmy się możliwemu planowi wykonania kilku zapytań.

Ćwiczenie 5: Indeks partycjonowany lokalnie

Spróbuj ponownie uruchomić zapytanie, tym razem przy użyciu indeksu partycjonowanego lokalnie w DateFrom .

Prawdopodobnie korzystałeś z tego planu wykonania:

 For all partitions where [StartDateFrom, StartDateTo) intersects ['2021-03-01', '2021-03-03'] Get first row from IX_DateFrom where DateFrom between '2021-03-01' and '2021-03-03' While found and DateFrom < '2021-03-04' Fetch Reservations.* where RowAddress = IX_DateFrom.RowAddress Write down HotelID, ReservationID, ClientID, DateFrom, DateTo Get next row from IX_DateFrom

Mamy szczęście, że wszystkie daty należą do jednej partycji, więc musieliśmy przejść tylko jeden lokalny indeks. Gdyby okres trwał sześć miesięcy, musielibyśmy odczytać sześć lokalnych indeksów.

Ćwiczenie 6: Kontrast

Twoim zadaniem jest ponowne użycie mapy partycji Rezerwacje, tym razem do sporządzenia listy okresów, w których Klient 124 odwiedził Hotel 1:

 SELECT DateFrom, DateTo FROM Reservations WHERE ClientID = 124 AND HotelID = 1;

Tutaj widzimy główną wadę indeksów lokalnych. Musieliśmy odczytać lokalny indeks IX_HotelID_CientID z każdej partycji tabeli Reservations :

 For all partitions Get first row from IX_HotelID_ClientID where ClientID = 124 and HotelID = 1 While found and ClientID = 124 and HotelID = 1 Fetch Reservations.* where RowAddress = IX_HotelID_ClientID.RowAddress Write down DateFrom, DateTo Get next row from IX_HotelID_ClientID

To wykonanie wyraźnie odczytałoby więcej bloków i zajęłoby więcej czasu, niż gdyby tabela nie była partycjonowana.

Dlatego chociaż znaleźliśmy sposób na utrzymanie kondycji naszych indeksów w okresie poza szczytem, ​​strategia spowolniła również niektóre z naszych zapytań.

Jeśli nasz model biznesowy pozwala nam zachować niewielką liczbę partycji lub przynajmniej najczęstsze zapytania zawierają kryteria umożliwiające RDBMS odczytanie tylko jednej lub dwóch partycji, to rozwiązanie może być tym, czego potrzebujemy. W przeciwnym razie lepiej unikajmy partycjonowania i pracujmy nad ulepszeniem modelu danych, indeksów i zapytań — oraz ulepszeniem serwera bazy danych.

Indeksy w SQL: Czego się nauczyć dalej

To już koniec naszej podróży. W SQL Indexes Explained skupiłem się na implementacji indeksu, która jest wspólna dla wszystkich nowoczesnych systemów RDBMS. Skupiłem się również na tematach, którymi interesują się programiści aplikacji, kosztem tematów, które zwykle dotyczą administratorów baz danych. To ostatnie przydałoby się zbadać wpływ współczynnika wypełnienia na fragmentację indeksu, ale osoby pełniące obie role prawdopodobnie uznają za przydatne dalsze czytanie o:

  • Buforowanie danych i indeksów
  • Struktury indeksów inne niż B-drzewa, takie jak hash, GiST, bitmapy i indeksy magazynu kolumn
  • Indeksy klastrowe (znane jako tabele zorganizowane według indeksów w Oracle)
  • Wskaźniki funkcjonalne
  • Indeksy częściowe

Omawiane przez nas podejście do partycjonowania to partycjonowanie zakresu . Jest to najczęściej używany typ partycjonowania, ale są też inne, takie jak partycjonowanie mieszające i partycjonowanie listy. Ponadto niektóre RDBMS oferują opcję wielu poziomów partycjonowania.

Wreszcie, programiści SQL dobrze zrobią, jeśli zbadają inne ważne tematy związane z wykonywaniem zapytań RDBMS — najpierw parsowanie zapytań, a następnie kompilację planów wykonania w oparciu o koszty, buforowanie i ponowne wykorzystanie.

W przypadku czterech RDMBS, z którymi mam doświadczenie, polecam te zasoby jako następne kroki:

Wyrocznia

  • Przegląd Optymalizatora
  • Indeksy i tablice zorganizowane według indeksu
  • Zarządzanie indeksami
  • Przegląd partycji
  • Przewodnik po partycjonowaniu
  • Zapytaj Toma

PostgreSQL

  • Przetwarzanie zapytań
  • Indeksy w PostgreSQL
  • Indeksy w PostgreSQL (oficjalna dokumentacja)
  • Zarządzanie buforami
  • Partycjonowanie tabeli
  • Przewodnik po partycjonowaniu

Microsoft SQL Server

  • Architektura przetwarzania zapytań
  • Indeksy
  • Partycjonowane tabele i indeksy

MySQL/MariaDB

  • Zrozumienie planu wykonania zapytania
  • Optymalizacja i indeksy
  • Partycjonowanie — podstawy
  • Partycjonowanie — dokumentacja
  • Dokumentacja MariaDB: Optymalizacja zapytań i indeksy