Zróbmy kilka założeń:
Mam stół, który wygląda następująco:
a | b
---+---
a | -1
a | 17
...
a | 21
c | 17
c | -3
...
c | 22
Fakty na temat mojego zestawu:
Rozmiar całego stołu wynosi ~ 10 10 rzędów.
Mam ~ 100 tys. Wierszy z wartością
a
w kolumniea
, podobnie dla innych wartości (npc
.).Oznacza to ~ 100 000 różnych wartości w kolumnie „a”.
Większość moich zapytań odczyta wszystkie lub większość wartości danej wartości w np
select sum(b) from t where a = 'c'
.Tabela jest napisana w taki sposób, że kolejne wartości są fizycznie blisko siebie (albo jest zapisywana w kolejności, albo zakładamy, że
CLUSTER
została użyta w tej tabeli i kolumniea
).Tabela jest rzadko aktualizowana, ale zależy nam tylko na prędkości odczytu.
Tabela jest stosunkowo wąska (powiedzmy ~ 25 bajtów na krotkę, + 23 bajty narzut).
Teraz pytanie brzmi: jakiego rodzaju indeksu powinienem używać? Rozumiem:
BTree Moim problemem jest to, że indeks BTree będzie ogromny, ponieważ o ile wiem, będzie przechowywać zduplikowane wartości (musi, ponieważ nie może założyć, że tabela jest fizycznie posortowana). Jeśli BTree jest ogromny, w końcu muszę przeczytać zarówno indeks, jak i części tabeli, na które wskazuje indeks. (Możemy użyć,
fillfactor = 100
aby nieco zmniejszyć rozmiar indeksu.)BRIN Rozumiem, że mogę tu mieć mały indeks kosztem czytania bezużytecznych stron. Użycie małej
pages_per_range
oznacza, że indeks jest większy (co jest problemem w BRIN, ponieważ muszę przeczytać cały indeks), posiadanie dużegopages_per_range
oznacza, że przeczytam wiele bezużytecznych stron. Czy istnieje magiczna formuła, która pozwala znaleźć dobrą wartośćpages_per_range
, biorąc pod uwagę te kompromisy?GIN / GiST Nie jestem pewien, czy są one istotne tutaj, ponieważ są one najczęściej używane do wyszukiwania pełnotekstowego, ale słyszę też, że dobrze radzą sobie z duplikatami kluczy. Czy pomoże tu
GIN
alboGiST
indeks?
Innym pytaniem jest, czy Postgres wykorzysta fakt, że tabela jest CLUSTER
edytowana (przy założeniu braku aktualizacji) w narzędziu do planowania zapytań (np. Przez binarne wyszukiwanie odpowiednich stron początkowych / końcowych)? W jakiś sposób spokrewnione, czy mogę po prostu przechowywać wszystkie moje kolumny w BTree i całkowicie upuścić tabelę (lub osiągnąć coś równoważnego, uważam, że są to indeksy klastrowe na serwerze SQL)? Czy jest jakiś hybrydowy indeks BTree / BRIN, który by tu pomógł?
Wolałbym unikać używania tablic do przechowywania moich wartości, ponieważ moje zapytanie skończy się w ten sposób mniej czytelne (rozumiem, że to zmniejszy koszt 23 bajtów narzut narzuty poprzez zmniejszenie liczby krotek).
Odpowiedzi:
Niekoniecznie - posiadanie indeksu btree „zakrywającego” będzie najszybszym czasem odczytu, a jeśli to wszystko, czego chcesz (tj. Jeśli możesz sobie pozwolić na dodatkowe miejsce), to jest to najlepszy wybór.
Jeśli nie możesz sobie pozwolić na obciążenie magazynu indeksem pokrywającym btree, BRIN jest dla Ciebie idealny, ponieważ masz już klastrowanie ( jest to bardzo ważne, aby BRIN był przydatny). Indeksy BRIN są małe , więc wszystkie strony prawdopodobnie będą w pamięci, jeśli wybierzesz odpowiednią wartość
pages_per_range
.Brak magicznej formuły, ale zacznij od
pages_per_range
nieco mniejszego niż średni rozmiar (w stronach) zajmowany przez średniąa
wartość. Prawdopodobnie próbujesz zminimalizować: (liczbę zeskanowanych stron BRIN) + (liczbę zeskanowanych stron sterty) dla typowego zapytania. PoszukajHeap Blocks: lossy=n
w planie wykonaniapages_per_range=1
i porównaj z innymi wartościamipages_per_range
- tzn. Sprawdź, ile skanowanych jest niepotrzebnych bloków sterty.Warto rozważyć GIN, ale prawdopodobnie nie GiST - jednak jeśli naturalne grupowanie jest naprawdę dobre, to BRIN będzie prawdopodobnie lepszym wyborem.
Oto przykładowe porównanie różnych typów indeksów dla danych fikcyjnych, trochę podobnych do twojego:
tabela i indeksy:
rozmiary relacji:
obejmujące btree:
zwykły btree:
BRIN pages_per_range = 4:
BRIN pages_per_range = 2:
GIN:
dbfiddle tutaj
źródło
Bitmap Index Scan
jako „przeczytaj cały indeks brina”, ale może to zły odczyt. WyroczniaCOMPRESS
wygląda na coś, co byłoby przydatne tutaj, ponieważ zmniejszyłoby rozmiar B-drzewa, ale utknąłem z pg!Oprócz btree i brin, które wydają się najbardziej sensownymi opcjami, niektóre inne, egzotyczne opcje, które mogą być warte zbadania - mogą być pomocne lub nie w twoim przypadku:
INCLUDE
indeksy . Będą - miejmy nadzieję - w kolejnej głównej wersji (10) Postgres, gdzieś około września 2017 r. Indeks on(a) INCLUDE (b)
ma taką samą strukturę jak indeks włączony,(a)
ale zawiera na stronach zb
listą wszystkie wartości (ale nieuporządkowane). Co oznacza, że nie możesz go użyć na przykład doSELECT * FROM t WHERE a = 'a' AND b = 2 ;
. Indeks może być użyty, ale podczas gdy(a,b)
indeks znajdzie pasujące wiersze z jednym wyszukiwaniem, indeks uwzględnienia będzie musiał przejść przez (ewentualnie 100K, jak w twoim przypadku) wartości, które pasująa = 'a'
i sprawdzićb
wartości.Z drugiej strony indeks jest nieco mniejszy od
(a,b)
indeksu i nie jest wymagana kolejność wb
celu obliczenia zapytaniaSUM(b)
. Możesz także mieć na przykład(a) INCLUDE (b,c,d)
które mogą być używane do zapytań podobnych do twoich, które agregują we wszystkich 3 kolumnach.Filtrowane (częściowe) indeksy . Sugestia, która na początku może wydawać się nieco szalona * :
Jeden indeks dla każdej
a
wartości. W twoim przypadku około 100 000 indeksów. Chociaż brzmi to dużo, należy wziąć pod uwagę, że każdy indeks będzie bardzo mały, zarówno pod względem wielkości (liczby wierszy), jak i szerokości (ponieważ będzie przechowywać tylkob
wartości). Jednak we wszystkich innych aspektach (indeksy 100K razem) będzie działał jak indeks b-drzewa(a,b)
podczas korzystania z przestrzeni(b)
indeksu.Wadą jest to, że musisz je tworzyć i utrzymywać samodzielnie, za każdym razem, gdy nowa wartość
a
jest dodawana do tabeli. Ponieważ twoja tabela jest raczej stabilna, bez wielu (lub jakichkolwiek) wstawek / aktualizacji, nie wydaje się to problemem.Tabele podsumowujące. Ponieważ tabela jest raczej stabilna, zawsze możesz utworzyć i wypełnić tabelę podsumowań najczęstszymi agregacjami, których potrzebujesz (
sum(b), sum(c), sum(d), avg(b), count(distinct b)
itp.). Będzie mały (tylko 100 000 wierszy) i będzie musiał zostać wypełniony tylko raz i zaktualizowany tylko wtedy, gdy wiersze zostaną wstawione / zaktualizowane / usunięte w głównej tabeli.*: pomysł skopiowany z tej firmy, która prowadzi 10 milionów indeksów w swoim systemie produkcyjnym: The Heap: Uruchamianie 10 milionów indeksów Postgresql w produkcji (i wciąż rośnie) .
źródło
SUM
jako przykładu, ale w praktyce moje zapytania nie mogą być wstępnieselect ... from t where a = '?' and ??
obliczone (są one bardziej jak wjere??
byłyby jakieś inne warunki zdefiniowane przez użytkownika.??
jest;)DO
instrukcja w tej pokrewnej odpowiedzi .