Kiedy należy używać indeksu złożonego?

139
  1. Kiedy należy używać indeksu złożonego w bazie danych?
  2. Jakie są konsekwencje wydajności przy użyciu indeksu złożonego)?
  3. Dlaczego powinienem używać indeksu złożonego?

Na przykład mam homesstół:

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  PRIMARY KEY  (`home_id`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
) ENGINE=InnoDB  ;

Czy ma sens stosowanie indeksu złożonego dla obu geolati geolng, takich jak:

Wymieniam:

  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),

z:

KEY `geolat_geolng` (`geolat`, `geolng`)

W takim razie:

  • Czemu?
  • Jakie są konsekwencje wydajności przy użyciu wskaźnika złożonego)?

AKTUALIZACJA:

Ponieważ wiele osób stwierdziło, że jest to całkowicie zależne od zapytań, które wykonuję, poniżej znajduje się najczęściej wykonywane zapytanie:

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

AKTUALIZACJA 2:

Z następującym schematem bazy danych:

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `primary_photo_group_id` int(10) unsigned NOT NULL default '0',
  `customer_id` bigint(20) unsigned NOT NULL,
  `account_type_id` int(11) NOT NULL,
  `address` varchar(128) collate utf8_unicode_ci NOT NULL,
  `city` varchar(64) collate utf8_unicode_ci NOT NULL,
  `state` varchar(2) collate utf8_unicode_ci NOT NULL,
  `zip` mediumint(8) unsigned NOT NULL,
  `price` mediumint(8) unsigned NOT NULL,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `num_of_beds` tinyint(3) unsigned NOT NULL,
  `num_of_baths` decimal(3,1) unsigned NOT NULL,
  `num_of_floors` tinyint(3) unsigned NOT NULL,
  `description` text collate utf8_unicode_ci,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  `display_status` tinyint(1) NOT NULL,
  `date_listed` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `contact_email` varchar(100) collate utf8_unicode_ci NOT NULL,
  `contact_phone_number` varchar(15) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`home_id`),
  KEY `customer_id` (`customer_id`),
  KEY `city` (`city`),
  KEY `num_of_beds` (`num_of_beds`),
  KEY `num_of_baths` (`num_of_baths`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
  KEY `account_type_id` (`account_type_id`),
  KEY `display_status` (`display_status`),
  KEY `sqft` (`sqft`),
  KEY `price` (`price`),
  KEY `primary_photo_group_id` (`primary_photo_group_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=8 ;

Używając następującego SQL:

EXPLAIN SELECT  homes.home_id,
                    address,
                    city,
                    state,
                    zip,
                    price,
                    sqft,
                    year_built,
                    account_type_id,
                    num_of_beds,
                    num_of_baths,
                    geolat,
                    geolng,
                    photo_id,
                    photo_url_dir
            FROM homes
            LEFT OUTER JOIN home_photos ON homes.home_id = home_photos.home_id
                AND homes.primary_photo_group_id = home_photos.home_photo_group_id
                AND home_photos.home_photo_type_id = 2
            WHERE homes.display_status = true
            AND homes.geolat BETWEEN -100 AND 100
            AND homes.geolng BETWEEN -100 AND 100

EXPLAIN zwraca:

id  select_type  table        type  possible_keys                                    key                  key_len  ref     rows  Extra
----------------------------------------------------------------------------------------------------------
1   SIMPLE       homes        ref   geolat,geolng,display_status                     display_status       1        const   2     Using where
1  SIMPLE        home_photos  ref   home_id,home_photo_type_id,home_photo_group_id   home_photo_group_id  4        homes.primary_photo_group_id   4  

Nie bardzo rozumiem, jak czytać polecenie EXPLAIN. Czy to wygląda dobrze, czy źle. W tej chwili NIE używam indeksu złożonego dla geolat i geolng. Czy powinienem być?

Miś
źródło

Odpowiedzi:

114

W przypadku korzystania z zapytań korzystających z tego indeksu należy używać indeksu złożonego. Złożony indeks, który wygląda następująco:

index( column_A, column_B, column_C )

przyniesie korzyści zapytaniu, które używa tych pól do łączenia, filtrowania, a czasem wybierania. Przyniesie to również korzyści zapytaniom, które używają podzbiorów kolumn położonych najbardziej po lewej stronie tego złożonego. Tak więc powyższy indeks również zaspokoi zapytania, które tego potrzebują

index( column_A, column_B, column_C )
index( column_A, column_B )
index( column_A )

Ale to nie będzie (przynajmniej nie bezpośrednio, może częściowo pomoże, jeśli nie ma lepszych indeksów) pomoc w zapytaniach, które wymagają

index( column_A, column_C )

Zwróć uwagę, że brakuje kolumny column_B.

W oryginalnym przykładzie indeks złożony dla dwóch wymiarów przyniesie korzyści głównie zapytaniom, które wykonują zapytania dotyczące obu wymiarów lub samego wymiaru po lewej stronie, ale nie samego wymiaru znajdującego się po prawej stronie. Jeśli zawsze wykonujesz zapytania w dwóch wymiarach, najlepszym rozwiązaniem jest indeks złożony, nie ma znaczenia, który jest pierwszy (najprawdopodobniej).

Mark Canlas
źródło
1
Mark, zaktualizowałem mój oryginalny post (aktualizacja 2). To jest moje rzeczywiste zapytanie. Mój rzeczywisty schemat bazy danych. I co zwraca polecenie EXPLAIN. Tak więc, mając te informacje - czy powinienem używać indeksu złożonego. Nadal jestem niejasny. Z góry dziękuję.
Teddy
Mark, czy złożony indeks w Twojej odpowiedzi jest zgodny z indeksem (kolumna_C)?
Boris D. Teoharov
Nie jestem pewien, czy rozumiem twoje pytanie. Ale jeśli pytasz, czy indeks (A, B, C) pomógłby zapytaniu filtrującemu w kolumnie C, odpowiedź zwykle brzmiałaby nie, nie używałby indeksu do filtrowania. Może jednak użyć indeksu, aby wyeliminować skanowanie tabeli, jeśli wybierasz tylko podzbiór ABC. Więc to jest inne, ale powiązane. Jednak w przypadku typowych zastosowań indeksów do włączania filtrowania odpowiedź brzmi: nie.
Mark Canlas
1
-1, ponieważ indeks złożony nie pomaga WHERE geolat BETWEEN ??? AND ??? AND geolng BETWEEN ??? AND ???. Zatrzyma się po pierwszym polu. Odpowiedź z „Przepełnienia pytań” wyjaśnia dlaczego.
Rick James
1
@felwithe MySQL może używać tylko jednego indeksu dla każdej z tabel w zapytaniu (istnieją wyjątki, np. łączenie indeksów). Co idealnie oznacza, że ​​tabela w zapytaniu musi używać jednego indeksu dla wszystkich klauzul gdzie, łączenia tabel, grupowania i porządkowania. Zatem oddzielny indeks w każdej kolumnie może nie działać zawsze, ale indeks złożony może zdziałać magię.
AKHIL MATHEW
59

Wyobraź sobie, że masz następujące trzy zapytania:

Zapytanie I:

SELECT * FROM homes WHERE `geolat`=42.9 AND `geolng`=36.4

Zapytanie II:

SELECT * FROM homes WHERE `geolat`=42.9

Zapytanie III:

SELECT * FROM homes WHERE `geolng`=36.4

Jeśli masz oddzielny indeks na kolumnę, wszystkie trzy zapytania używają indeksów. W MySQL, jeśli masz indeks złożony ( geolat, geolng), tylko zapytanie I i zapytanie II (które używa pierwszej części indeksu złożonego) używa indeksów. W tym przypadku zapytanie III wymaga pełnego przeszukania tabeli.

Na kilku kolumnach indeksy części instrukcji, jest jasno wyjaśnione w jaki sposób działa wiele indeksów kolumn, więc nie chcę wpisywać ręcznie.

Ze strony podręcznika MySQL :

Indeks wielokolumnowy można uznać za posortowaną tablicę zawierającą wartości, które są tworzone przez konkatenację wartości indeksowanych kolumn .

Jeśli używasz oddzielnego indeksu dla kolumn geolat i geolng, masz w tabeli dwa różne indeksy, które możesz przeszukiwać niezależnie.

INDEX geolat
-----------
VALUE RRN
36.4  1
36.4  8
36.6  2
37.8  3
37.8  12
41.4  4

INDEX geolng
-----------
VALUE RRN
26.1  1
26.1  8
29.6  2
29.6  3
30.1  12
34.7  4

Jeśli używasz indeksu złożonego, masz tylko jeden indeks dla obu kolumn:

INDEX (geolat, geolng)
-----------
VALUE      RRN
36.4,26.1  1
36.4,26.1  8
36.6,29.6  2
37.8,29.6  3
37.8,30.1  12
41.4,34.7  4

RRN to względny numer rekordu (dla uproszczenia można powiedzieć ID). Pierwsze dwa indeksy zostały wygenerowane oddzielnie, a trzeci indeks jest złożony. Jak widać, możesz wyszukiwać na podstawie geolng na złożonym, ponieważ jest indeksowany przez geolat, jednak możliwe jest wyszukiwanie według geolat lub „geolat AND geolng” (ponieważ geolng jest indeksem drugiego poziomu).

Również spojrzeć w jaki sposób MySQL używa indeksów sekcja obsługi.

Emre Yazici
źródło
1
Właściwie nie mam żadnego z tych zapytań. Moje zapytanie jest wymienione w oryginalnym poście. Moje pytanie dotyczy zwrócenia domów w kwadratowej siatce. Znam się na przestrzenności i nie próbuję obliczać odległości. Chcę po prostu wiedzieć, czy użycie indeksu złożonego ma sens, gdy próbuję wyświetlić wszystkie domy w określonej siatce geograficznej (np. Sąsiedztwo / miasto / hrabstwo)
Teddy
Eyazici, zaktualizowałem mój oryginalny post (aktualizacja 2). To jest moje rzeczywiste zapytanie. Mój rzeczywisty schemat bazy danych. I co zwraca polecenie EXPLAIN. Tak więc, mając te informacje - czy powinienem używać indeksu złożonego. Nadal jestem niejasny. Z góry dziękuję
Teddy
@ „Właściwie to nie mam żadnego z tych zapytań.”. Właściwie masz, użyłem prostego warunku WHERE do wyjaśnienia podstawowej logiki. Używając warunku (np. GDZIE) w kolumnie, MySQL stara się używać indeksów, gdy tylko jest to możliwe. „x BETWEEN a AND b” jest podobne do „x> a AND x <b”. W zapytaniu warunkowym użyto zarówno kolumn geolng, jak i geolat. Jeśli używasz indeksu kompozycji „(geolat, geolng)” Twój „AND geolng BETWEEN ??? AND ???” warunkowy nie zyskuje zalet indeksu (dotyczy MySQL). Dlatego w scenariuszu należy użyć oddzielnego indeksu na kolumnę.
Emre Yazici
Nie rozumiem. Dlaczego powinienem używać oddzielnych indeksów dla geolat i geolng, skoro ZAWSZE wykonuję zapytanie zawierające obie kolumny
Teddy
1
Nie. W przypadku napotkania „zakresu” (jak w przypadku BETWEEN), dalsze pola indeksu nie są brane pod uwagę! Więc indeks złożony nie jest lepszy.
Rick James
19

Może istnieć błędne przekonanie na temat tego, co robi indeks złożony. Wiele osób uważa, że ​​indeks złożony może być użyty do optymalizacji zapytania wyszukiwania, o ile whereklauzula obejmuje indeksowane kolumny, w twoim przypadku geolati geolng. Zagłębmy się głębiej:

Uważam, że dane dotyczące współrzędnych domów byłyby przypadkowymi miejscami dziesiętnymi:

home_id  geolat  geolng
   1    20.1243  50.4521
   2    22.6456  51.1564
   3    13.5464  45.4562
   4    55.5642 166.5756
   5    24.2624  27.4564
   6    62.1564  24.2542
...

Ponieważ geolati geolngwartości prawie się nie powtarzają. Złożony indeks geolati geolngwyglądałby mniej więcej tak:

index_id  geolat  geolng
   1     20.1243  50.4521
   2     20.1244  61.1564
   3     20.1251  55.4562
   4     20.1293  66.5756
   5     20.1302  57.4564
   6     20.1311  54.2542
...

Dlatego druga kolumna indeksu złożonego jest w zasadzie bezużyteczna ! Szybkość zapytania z indeksem złożonym prawdopodobnie będzie podobna do indeksu tylko dla geolatkolumny.

Jak wspomniał Will, MySQL zapewnia obsługę rozszerzeń przestrzennych . Punkt przestrzenny jest przechowywany w jednej kolumnie zamiast w dwóch oddzielnych lat lngkolumnach. Do takiej kolumny można zastosować indeks przestrzenny. Jednak skuteczność mogłaby być przeceniona na podstawie moich osobistych doświadczeń. Możliwe, że indeks przestrzenny nie rozwiązuje problemu dwuwymiarowego, a jedynie przyspiesza wyszukiwanie przy użyciu R-drzew z podziałem kwadratowym .

Kompromis polega na tym, że punkt przestrzenny zużywa znacznie więcej pamięci, ponieważ do przechowywania współrzędnych używa ośmiobajtowych liczb o podwójnej precyzji. Popraw mnie, jeśli się mylę.

Przepełnienie pytań
źródło
6

Indeksy złożone są przydatne w przypadku

  • 0 lub więcej klauzul „=” plus
  • co najwyżej jedna klauzula zakresu.

Indeks złożony nie może obsługiwać dwóch zakresów. Omawiam to dalej w mojej indeksowej książce kucharskiej .

Znajdź najbliższy - jeśli naprawdę chodzi o optymalizację

WHERE geolat BETWEEN ??? AND ???
  AND geolng BETWEEN ??? AND ???

wtedy żaden indeks nie może obsłużyć obu wymiarów.

Zamiast tego trzeba „myśleć nieszablonowo”. Jeśli jeden wymiar jest implementowany poprzez partycjonowanie, a drugi przez ostrożne wybieranie PRIMARY KEY, można uzyskać znacznie lepszą wydajność dla bardzo dużych tabel wyszukiwania lat / lng. Mój blog latlng zawiera szczegółowe informacje o tym, jak zaimplementować funkcję „znajdź najbliższy” na świecie. Zawiera kod.

PARTITIONsto pasma szerokości geograficznych. PRIMARY KEYCelowo rozpoczyna się od długości tak, że użyteczne wiersze mogą być w tym samym bloku. Przechowywana rutyna organizuje niechlujny kod do wykonywania order by... limit...i powiększania „kwadratu” wokół celu, dopóki nie będzie wystarczającej liczby kawiarni (lub czegokolwiek). Zajmuje się również obliczeniami po ortodromie oraz obsługą linii danych i biegunów.

Jeszcze

Napisałem innego bloga; porównuje 5 sposobów wyszukiwania lat / lng: http://mysql.rjweb.org/doc.php/latlng#representation_choices (odwołuje się do linku podanego powyżej jako jednego z 5). Jednym z innych sposobów jest: i wskazuje, że są one optymalne dla konkretnego przypadku :

INDEX(geolat, geolng),
INDEX(geolng, geolat)

Oznacza to, że ważne jest posiadanie obu kolumn w dwóch indeksach i brak indeksów jednokolumnowych w geolat i geolng.

Rick James
źródło
5

Indeksy złożone mają bardzo duże możliwości, ponieważ:

  • Wymuszaj integralność struktury
  • Włącz sortowanie według FILTROWANEGO identyfikatora

ZACHOWAJ INTEGRALNOŚĆ STRUKTURY

Indeksy złożone to nie tylko inny typ indeksów; mogą zapewnić KONIECZNĄ strukturę tabeli, wymuszając integralność jako klucz podstawowy.

Mysql's Innodb obsługuje klastrowanie, a poniższy przykład ilustruje, dlaczego indeks złożony może być konieczny.

Aby utworzyć przyjaciół tabel (czyli do sieci społecznościowej) musimy 2 kolumny: user_id, friend_id.

Struktura stołu

user_id (medium_int)
friend_id (medium_int)

Primary Key -> (user_id, friend_id)

Z racji tego, klucz podstawowy (PK) jest unikalny i tworząc złożony PK, Innodb automatycznie sprawdzi, czy user_id, friend_idpo dodaniu nowego rekordu nie ma duplikatów . Jest to oczekiwane zachowanie, ponieważ na przykład żaden użytkownik nie powinien mieć więcej niż 1 rekord (łącze relacji) friend_id = 2.

Bez złożonej PK możemy utworzyć ten schemat przy użyciu klucza zastępczego:

user_friend_id
user_id
friend_id

Primary Key -> (user_friend_id)

Teraz za każdym razem, gdy dodawany jest nowy rekord, będziemy musieli sprawdzić, czy poprzedni rekord z kombinacją user_id, friend_idjuż nie istnieje.

Jako taki, indeks złożony może wymusić integralność struktury.

WŁĄCZ SORTOWANIE NA FILTROWANYM IDENTYFIKATORZE

Bardzo często zestaw rekordów jest sortowany według czasu publikacji (znacznik czasu lub data i godzina). Zwykle oznacza to wysyłanie na podany identyfikator. Oto przykład

Tabela User_Wall_Posts (pomyśl o postach na ścianie Facebooka)

user_id (medium_int)
timestamp (timestamp)
author_id (medium_int)
comment_post (text)

Primary Key -> (user_id, timestamp, author_id)

Chcemy przeszukiwać i znajdować wszystkie posty dla user_id = 10i sortować komentarze według timestamp(data).

ZAPYTANIE SQL

SELECT * FROM User_Wall_Posts WHERE user_id = 10 ORDER BY timestamp DES

Złożona PK umożliwia Mysql filtrowanie i sortowanie wyników przy użyciu indeksu; MySQL nie będzie musiał używać pliku tymczasowego ani sortowania plików do pobrania wyników. Bez klucza złożonego nie byłoby to możliwe i spowodowałoby bardzo nieefektywne zapytanie.

W związku z tym klucze złożone są bardzo potężne i pasują bardziej niż prosty problem „Chcę wyszukać, column_a, column_bwięc użyję kluczy złożonych. W moim obecnym schemacie bazy danych mam tyle samo kluczy złożonych, co pojedynczych kluczy. Nie przeocz zastosowanie klucza złożonego!

ProfileTwist
źródło
PRIMARY KEYWymusza integralności ze względu na to UNIQUE, bycie złożonym jest drugorzędne.
Rick James
1

Do wyszukiwania przestrzennego potrzebny jest algorytm R-Tree , który umożliwia bardzo szybkie przeszukiwanie obszarów geograficznych. Dokładnie to, czego potrzebujesz do tej pracy.

Niektóre bazy danych mają wbudowane indeksy przestrzenne. Szybkie wyszukiwanie w Google pokazuje, że MySQL 5 je ma (patrząc na twój SQL domyślam się, że używasz MySQL).

Będzie
źródło
1

Indeks złożony może być przydatny, gdy chcesz zoptymalizować group byklauzulę (sprawdź ten artykuł http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html ). Proszę uważać:

Najważniejszymi warunkami korzystania z indeksów dla GROUP BY jest to, że wszystkie kolumny GROUP BY odwołują się do atrybutów z tego samego indeksu i że indeks przechowuje swoje klucze w kolejności (na przykład jest to indeks BTREE, a nie indeks HASH)

Aleksandra
źródło
GROUP BYnie została wymieniona.
Rick James
Nie wspomniano, gdzie? :) Jest to oczywiście wspomniane w artykule, o którym wspomniałem. I odpowiada na pytania, które zostały zadane: Kiedy powinienem używać indeksu złożonego w bazie danych? Jakie są konsekwencje wydajności przy użyciu indeksu złożonego)? Dlaczego powinienem używać indeksu złożonego?
Alexander
Korekta: GROUP BYnie została wymieniona w PO.
Rick James
Jasne, taka była odpowiedź - jeden z przypadków, w których używalibyśmy indeksu złożonego w bazie danych.
Alexander
1

Nie ma czarno-białego, jeden rozmiar pasuje do wszystkich.

Należy użyć indeksu złożonego (lub wielokolumnowego), jeśli obciążenie pracą zapytania przyniesie korzyści.

Aby to ustalić, musisz sprofilować obciążenie pracą zapytania.

Indeks złożony wchodzi w grę, gdy zapytania mogą być w całości spełnione z tego indeksu: co oznacza, że ​​wszystkie kolumny wymagane przez zapytanie są objęte (pokryte) przez indeks.

UPDATE (w odpowiedzi na edycję wysłanego pytania): Jeśli wybierasz * z tabeli, można użyć indeksu złożonego, ale nie może. Aby mieć pewność, musisz uruchomić EXPLAIN PLAN .

Mitch Wheat
źródło
Czy ma sens stosowanie indeksu złożonego dla danych dotyczących lokalizacji geograficznej (szerokość i długość geograficzna)?
Teddy
1
Zależy to całkowicie od zapytań kierowanych do tej tabeli.
Mitch Wheat
Zaktualizowałem mój oryginalny post, aby zawierał najczęściej wykonywane zapytanie. Patrz wyżej.
Teddy
@MitchWheat - „Covering”, a nie „composite” występuje wtedy, gdy zapytanie może zostać w całości spełnione z poziomu indeksu. (Indeks „pokrywający” jest zwykle „złożony”).
Rick James,
@Teddy - zobacz mysql.rjweb.org/doc.php/find_nearest_in_mysql#bounding_box, aby uzyskać informacje o geo.
Rick James
0

Jestem z @Mitch, zależy wyłącznie od twoich zapytań. Na szczęście możesz tworzyć i usuwać indeksy w dowolnym momencie oraz możesz dołączyć słowo kluczowe EXPLAIN do swoich zapytań, aby sprawdzić, czy analizator zapytań używa indeksów.

Jeśli szukasz dokładnej pary długich i długich, ten indeks prawdopodobnie miałby sens. Ale prawdopodobnie będziesz szukać domów w pewnej odległości od określonego miejsca, więc Twoje zapytania będą wyglądać mniej więcej tak (patrz źródło ):

select *, sqrt(  pow(h2.geolat - h1.geolat,  2) 
               + pow(h2.geolng - h1.geolng, 2) ) as distance
from homes h1, homes h2
where h1.home_id = 12345 and h2.home_id != h1.home_id
order by distance

a indeks najprawdopodobniej nie będzie w ogóle pomocny. Dla zapytań geoprzestrzennych, trzeba coś jak ten .

Aktualizacja: za pomocą tego zapytania:

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

Analizator zapytań mógłby użyć indeksu samego geolat lub indeksu samego geolng, lub ewentualnie obu indeksów. Nie sądzę, by użyłby indeksu złożonego. Ale łatwo jest wypróbować każdą z tych permutacji na rzeczywistym zbiorze danych, a następnie (a) zobaczyć, co powie Ci EXPLAIN i (b) zmierzyć czas, jaki naprawdę zajmuje zapytanie.

Jim Ferrans
źródło
Po prostu używam chęci zwrotu domów w kwadratowej siatce. Wiem o przestrzenności, więc nie próbuję obliczać odległości. Chcę po prostu wrócić do domów w kwadratowej siatce i chcę, aby to działało szybko. W związku z tym chcę się upewnić, że moje indeksy są poprawnie skonfigurowane. To pomaga?
Teddy