Oto wyniki zapytania do tabeli w drugiej kolumnie indeksu wielokolumnowego .
Efekty są łatwe do odtworzenia dla każdego. Po prostu spróbuj w domu.
Testowałem z PostgreSQL 9.0.5 na Debianie, używając średniej wielkości tabeli bazy danych z 23322 wierszami. Implementuje relację n: m między tabelami adr
(adres) i att
(atrybut), ale tutaj nie ma to znaczenia. Uproszczony schemat:
CREATE TABLE adratt (
adratt_id serial PRIMARY KEY
, adr_id integer NOT NULL
, att_id integer NOT NULL
, log_up timestamp(0) NOT NULL DEFAULT (now())::timestamp(0)
, CONSTRAINT adratt_uni UNIQUE (adr_id, att_id)
);
UNIQUE
Ograniczenie skutecznie realizuje unikalną indeksu. Dla pewności powtórzyłem test z prostym indeksem i uzyskałem identyczne wyniki, jak oczekiwano.
CREATE INDEX adratt_idx ON adratt(adr_id, att_id)
Tabela jest skupiona na adratt_uni
indeksie, a przed testem uruchomiłem:
CLUSTER adratt;
ANALYZE adratt;
Sekwencyjne skanowanie zapytań (adr_id, att_id)
jest tak szybkie, jak to tylko możliwe. Indeks wielokolumnowy będzie nadal używany jako warunek zapytania w samej drugiej kolumnie indeksu.
Uruchomiłem zapytania kilka razy, aby zapełnić pamięć podręczną i wybrałem najlepszy z dziesięciu przebiegów, aby uzyskać porównywalne wyniki.
1. Zapytanie za pomocą obu kolumn
SELECT *
FROM adratt
WHERE att_id = 90
AND adr_id = 10;
adratt_id | adr_id | att_id | log_up
-----------+--------+--------+---------------------
123 | 10 | 90 | 2008-07-29 09:35:54
(1 row)
Wyjście EXPLAIN ANALYZE
:
Index Scan using adratt_uni on adratt (cost=0.00..3.48 rows=1 width=20) (actual time=0.022..0.025 rows=1 loops=1)
Index Cond: ((adr_id = 10) AND (att_id = 90))
Total runtime: 0.067 ms
2. Zapytanie za pomocą pierwszej kolumny
SELECT * FROM adratt WHERE adr_id = 10
adratt_id | adr_id | att_id | log_up
-----------+--------+--------+---------------------
126 | 10 | 10 | 2008-07-29 09:35:54
125 | 10 | 13 | 2008-07-29 09:35:54
4711 | 10 | 21 | 2008-07-29 09:35:54
29322 | 10 | 22 | 2011-06-06 15:50:38
29321 | 10 | 30 | 2011-06-06 15:47:17
124 | 10 | 62 | 2008-07-29 09:35:54
21913 | 10 | 78 | 2008-07-29 09:35:54
123 | 10 | 90 | 2008-07-29 09:35:54
28352 | 10 | 106 | 2010-11-22 12:37:50
(9 rows)
Wyjście EXPLAIN ANALYZE
:
Index Scan using adratt_uni on adratt (cost=0.00..8.23 rows=9 width=20) (actual time=0.007..0.023 rows=9 loops=1)
Index Cond: (adr_id = 10)
Total runtime: 0.058 ms
3. Zapytanie za pomocą drugiej kolumny
SELECT * FROM adratt WHERE att_id = 90
adratt_id | adr_id | att_id | log_up
-----------+--------+--------+---------------------
123 | 10 | 90 | 2008-07-29 09:35:54
180 | 39 | 90 | 2008-08-29 15:46:07
...
(83 rows)
Wyjście EXPLAIN ANALYZE
:
Index Scan using adratt_uni on adratt (cost=0.00..818.51 rows=83 width=20) (actual time=0.014..0.694 rows=83 loops=1)
Index Cond: (att_id = 90)
Total runtime: 0.849 ms
4. Wyłącz indeksowanie i skanowanie bitmaps
SET enable_indexscan = off;
SELECT * FROM adratt WHERE att_id = 90
Dane wyjściowe EXPLAIN ANALYZE:
Bitmap Heap Scan on adratt (cost=779.94..854.74 rows=83 width=20) (actual time=0.558..0.743 rows=83 loops=1)
Recheck Cond: (att_id = 90)
-> Bitmap Index Scan on adratt_uni (cost=0.00..779.86 rows=83 width=0) (actual time=0.544..0.544 rows=83 loops=1)
Index Cond: (att_id = 90)
Total runtime: 0.894 ms
SET enable_bitmapscan = off
SELECT * FROM adratt WHERE att_id = 90
Wyjście EXPLAIN ANALYZE
:
Seq Scan on adratt (cost=0.00..1323.10 rows=83 width=20) (actual time=0.009..2.429 rows=83 loops=1)
Filter: (att_id = 90)
Total runtime: 2.680 ms
Wniosek
Zgodnie z oczekiwaniami indeks wielokolumnowy służy do zapytania tylko w drugiej kolumnie.
Zgodnie z oczekiwaniami jest mniej skuteczny, ale zapytanie jest nadal 3 razy szybsze niż bez indeksu.
Po wyłączeniu skanowania indeksów narzędzie do planowania zapytań wybiera skanowanie sterty map bitowych, które wykonuje prawie tak samo szybko. Dopiero po wyłączeniu to wraca do skanowania sekwencyjnego.
vacuum full
areindex
. Inne niż to pomoże skanowania indeksu na pierwszej lub obu czołowych kolumnach dużo , ale boli zapytań na drugiej kolumnie. W świeżo klastrowanej tabeli wiersze o tej samej wartości w drugiej kolumnie są rozłożone, tak że trzeba będzie odczytać maksymalnie bloki.re 1) Tak i nie.
W przypadku zapytania, które wykorzystuje obie kolumny, np
where (user_id1, user_id2) = (1,2)
. Nie ma znaczenia, który indeks jest tworzony.W przypadku zapytania, które ma warunek tylko na jednej kolumnie, np
where user_id1 = 1
. Ma to znaczenie, ponieważ zwykle do porównania przez optymalizator można użyć tylko „wiodących” kolumn.where user_id1 = 1
Byłby więc w stanie używać indeksu (identyfikator_użytkownika1, identyfikator_użytkownika2), ale nie byłby w stanie indeksować (identyfikator_użytkownika2, identyfikator_użytkownika1) we wszystkich przypadkach.Po zabawie z tym (po tym, jak Erwin tak uprzejmie pokazał nam konfigurację, w której to działa), wydaje się, że zależy to w dużej mierze od dystrybucji danych w drugiej kolumnie, chociaż jeszcze nie dowiedziałem się, która sytuacja umożliwia optymalizatorowi użycie końcowych kolumn na warunek GDZIE.
Oracle 11, który może również (czasami) używać kolumn, które nie znajdują się na początku definicji indeksu.
re 2) Tak, utworzy indeks
Cytat z instrukcji
re 2a)
Primary Key (user_id1,user_id2)
utworzy indeks na (identyfikator_użytkownika1, identyfikator_użytkownika2) (którego można bardzo łatwo dowiedzieć się, po prostu tworząc taki klucz podstawowy)Gorąco polecam przeczytanie rozdziału o indeksach w podręczniku , ponieważ w zasadzie odpowiada on na wszystkie powyższe pytania.
Dodatkowo, jaki indeks utworzyć? autor: depesz wykonuje dobrą robotę wyjaśniając porządek w kolumnach indeksu i innych tematach związanych z indeksem.
źródło
Ad 1)
Istnieją ograniczenia w PostgreSQL, takie jak @a_horse_with_no_name . Do wersji 8.0 indeksy wielokolumnowe mogły być używane tylko do zapytań dotyczących wiodących kolumn. Zostało to poprawione w wersji 8.1. Aktualna instrukcja dla PostgreSQL 10 (aktualizacja) wyjaśnia:
Podkreśl moje. Mogę to potwierdzić z doświadczenia.
Zobacz także przypadek testowy, który dodał moją późniejszą odpowiedź tutaj .
źródło
To jest odpowiedź na odpowiedź Jacka , komentarz nie zrobiłby tego.
Przed wersją 9.2 nie było indeksów pokrywających w PostgreSQL . Ze względu na model MVCC każdą krotkę w zestawie wyników należy odwiedzić, aby sprawdzić widoczność. Być może myślisz o Oracle.
Programiści PostgreSQL mówią o „skanach indeksowych” . W rzeczywistości funkcja ta została wydana wraz z Postgres 9.2. Przeczytaj komunikat zatwierdzenia .
Depesz napisał bardzo pouczający post na blogu .
Prawdziwe indeksy pokrycia (aktualizacja) zostały wprowadzone wraz z
INCLUDE
klauzulą dotyczącą Postgres 11. Powiązane:To też jest trochę nieprzyjemne:
Jak podano w komentarzach do mojej drugiej odpowiedzi, przeprowadziłem również testy z tabelą dwóch liczb całkowitych i niczym więcej. Indeks zawiera te same kolumny co tabela. Rozmiar indeksu btree wynosi około 2/3 wielkości tabeli. Za mało, aby wyjaśnić przyspieszenie czynnika 3. Przeprowadziłem więcej testów, w oparciu o twoją konfigurację, uproszczonych do dwóch kolumn i 100000 wierszy. W mojej instalacji PostgreSQL 9.0 wyniki były spójne.
Jeśli tabela ma dodatkowe kolumny, przyspieszenie z indeksem staje się bardziej znaczące, ale z pewnością nie jest to jedyny czynnik .
Podsumowując główne punkty:
Indeksy wielokolumnowe mogą być używane z zapytaniami dotyczącymi nieprzedstawiających się kolumn, ale przyspieszenie wynosi tylko około 3 dla kryteriów selektywnych (mały procent wierszy w wyniku). Wyższy dla większych krotek, niższy dla większych części tabeli w zestawie wyników.
Utwórz dodatkowy indeks w tych kolumnach, jeśli wydajność jest ważna.
Jeśli wszystkie zaangażowane kolumny są uwzględnione w indeksie (indeks obejmujący), a wszystkie zaangażowane wiersze (na blok) są widoczne dla wszystkich transakcji, można uzyskać „skanowanie tylko indeksu” na stronie 9.2 lub nowszej.
źródło
Nie są one równoważne i ogólnie mówiąc indeks (bar, baz) nie będzie skuteczny w przypadku zapytań formularza
select * from foo where baz=?
Erwin wykazał, że takie indeksy rzeczywiście mogą przyspieszyć zapytanie, ale ten efekt jest ograniczony i nie ma takiej samej kolejności, jak zwykle oczekuje się, że indeks poprawi wyszukiwanie - opiera się na fakcie, że „pełne skanowanie” indeksu jest często szybsze niż „pełne skanowanie” indeksowanej tabeli ze względu na dodatkowe kolumny w tabeli, które nie pojawiają się w indeksie.
Podsumowanie: indeksy mogą pomagać w zapytaniach nawet w kolumnach nie prowadzących, ale na jeden z dwóch drugorzędnych i względnie drobnych sposobów, a nie w dramatyczny sposób, zwykle oczekuje się, że indeks pomoże z powodu jego struktury btree
nb dwa sposoby, w jakie indeks może pomóc, jeśli pełne skanowanie indeksu jest znacznie tańsze niż pełne skanowanie tabeli i: 1. wyszukiwanie tabel jest tanie (ponieważ jest ich niewiele lub są one zgrupowane),
lub 2. Indeks obejmuje, więc nie ma wyszukiwania tabel we wszystkich przypadkach, patrz komentarze Erwins tutajtestbed:
zapytanie 1 (bez indeksu, trafienie w 74 bufory ):
zapytanie 2 (z indeksem - optymalizator ignoruje indeks - ponownie uderza 74 bufory ):
zapytanie 2 (z indeksem - i oszukujemy optymalizatora, aby go użył):
Zatem dostęp za pośrednictwem indeksu jest w tym przypadku dwukrotnie szybszy niż w przypadku 30 buforów - co pod względem indeksowania jest „nieco szybsze” !, i YMMV w zależności od względnego rozmiaru tabeli i indeksu, wraz z liczbą przefiltrowanych wierszy i cech klastrowania danych w tabeli
Natomiast zapytania w kolumnie wiodącej wykorzystują strukturę btree indeksu - w tym przypadku uderzając w 2 bufory :
źródło