Quando dovrei usare un indice composito?


133
  1. Quando dovrei usare un indice composito in un database?
  2. Quali sono le conseguenze delle prestazioni utilizzando un indice composito)?
  3. Perché dovrei usare un indice composito?

Ad esempio, ho una homestabella:

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  ;

Ha senso usare un indice composito per entrambi geolate geolng, in modo tale che:

Sostituisco:

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

con:

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

Se è così:

  • Perché?
  • Qual è la ramificazione delle prestazioni utilizzando un indice composito)?

AGGIORNARE:

Poiché molte persone hanno dichiarato che dipende interamente dalle query che eseguo, di seguito è la query più comune eseguita:

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

AGGIORNAMENTO 2:

Con il seguente schema di database:

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 ;

Utilizzando il seguente 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

SPIEGAZIONE restituisce:

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  

Non capisco bene come leggere il comando EXPLAIN. Questo sembra buono o cattivo? In questo momento, NON sto usando un indice composito per geolat e geolng. Dovrei essere?

Risposte:


111

È necessario utilizzare un indice composito quando si utilizzano query che ne beneficiano. Un indice composito che assomiglia a questo:

index( column_A, column_B, column_C )

beneficerà di una query che utilizza quei campi per unire, filtrare e talvolta selezionare. Beneficerà anche le query che utilizzano i sottoinsiemi di colonne più a sinistra in quel composito. Quindi l'indice sopra soddisferà anche le richieste che richiedono

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

Ma non (almeno non direttamente, forse può aiutare parzialmente se non ci sono indici migliori) aiutare per le domande che necessitano

index( column_A, column_C )

Notare come manca column_B.

Nel tuo esempio originale, un indice composito per due dimensioni beneficerà principalmente di query che eseguono query su entrambe le dimensioni o sulla dimensione più a sinistra da sola, ma non sulla dimensione più a destra da sola. Se stai sempre interrogando due dimensioni, un indice composito è la strada da percorrere, non importa quale sia il primo (molto probabilmente).


1
Mark, ho aggiornato il mio post originale (aggiornamento 2). Questa è la mia vera domanda. Il mio attuale schema db. E cosa restituisce il comando EXPLAIN. Quindi, con queste informazioni, dovrei usare un indice composito. Non sono ancora chiaro. Grazie in anticipo.
Teddy

Mark, l'indice composito nella tua risposta soddisfa l'indice (colonna_C)?
Boris D. Teoharov,

Non sono sicuro di aver capito la tua domanda. Ma, se stai chiedendo se l'indice (A, B, C) aiuterebbe una query che filtra sulla colonna C, la risposta sarebbe in genere no, non userebbe l'indice per il filtro. Potrebbe tuttavia utilizzare l'indice per eliminare una scansione di tabella se si seleziona solo su un sottoinsieme di ABC. Quindi, è diverso, ma correlato. Ma per gli usi tipici degli indici per abilitare il filtro, la risposta è no.
Mark Canlas,

1
-1 perché un indice composito non aiuta WHERE geolat BETWEEN ??? AND ??? AND geolng BETWEEN ??? AND ???. Si fermerà dopo il primo campo. La risposta da "Overflow di domande" spiega perché.
Rick James,

1
@felwithe MySQL può utilizzare solo un indice per ciascuna delle tabelle in una query (ci sono esenzioni, ad es. unione di indici). Ciò significa idealmente che una tabella in una query deve utilizzare un singolo indice per tutte le clausole where, join tabella, raggruppamento e ordinamento. Quindi un indice separato su ogni colonna potrebbe non funzionare sempre, ma un indice composito può fare la magia.
AKHIL MATHEW, il

57

Immagina di avere le tre query seguenti:

Quesito I:

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

Query II:

SELECT * FROM homes WHERE `geolat`=42.9

Query III:

SELECT * FROM homes WHERE `geolng`=36.4

Se si dispone di un indice separato per colonna, tutte e tre le query utilizzano gli indici. In MySQL, se si dispone di un indice composito ( geolat, geolng), solo la query I e la query II (che utilizza la prima parte dell'indice composito) utilizzano gli indici. In questo caso, la query III richiede la ricerca della tabella completa.

Nella sezione degli indici a più colonne del manuale, viene spiegato chiaramente come funzionano gli indici a più colonne, quindi non voglio riscrivere il manuale.

Dalla pagina del manuale di riferimento di MySQL :

Un indice a più colonne può essere considerato un array ordinato contenente valori creati concatenando i valori delle colonne indicizzate .

Se si utilizza l'indice separato per le colonne geolat e geolng, nella tabella sono presenti due indici diversi che è possibile cercare in modo indipendente.

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

Se usi l'indice composito hai un solo indice per entrambe le colonne:

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 è il numero di record relativo (per semplificare, puoi dire ID). I primi due indici generati separatamente e il terzo indice è composto. Come puoi vedere, puoi effettuare una ricerca in base al geolng su quello composito poiché è indicizzato da geolat, tuttavia è possibile effettuare la ricerca per geolat o "geolat AND geolng" (poiché geolng è un indice di secondo livello).

Inoltre, dai un'occhiata alla sezione del manuale su come MySQL utilizza gli indici .


1
In realtà, non ho nessuna di quelle domande. La mia query è elencata nel post originale. La mia richiesta è di restituire case all'interno di una griglia quadrata. Conosco lo spazio e non sto cercando di calcolare le distanze. Voglio semplicemente sapere se usare un indice composito ha senso quando sto cercando di visualizzare tutte le case all'interno di una particolare griglia geografica (ad es. Quartiere / città / contea)
Teddy

Eyazici, ho aggiornato il mio post originale (aggiornamento 2). Questa è la mia vera domanda. Il mio attuale schema db. E cosa restituisce il comando EXPLAIN. Quindi, con queste informazioni, dovrei usare un indice composito. Non sono ancora chiaro. Grazie in anticipo
Teddy

@ "In realtà, non ho nessuna di quelle domande.". In realtà, ho usato la semplice condizione WHERE per spiegare la logica di base. Quando si utilizza un condizionale (es. WHERE) su una colonna, MySQL tenta di utilizzare gli indici ogni volta che è possibile. "x TRA a AND b" è simile a "x> a AND x <b". Hai utilizzato entrambe le colonne geolng e geolat nella tua query condizionale. Se usi l'indice composito "(geolat, geolng)" tuo "AND geolng TRA ??? AND ???" condizionale non ottiene vantaggi di indice (questo è per MySQL). Quindi dovresti usare un indice separato per colonna per il tuo scenario.
Emre Yazici,

Non capisco. Perché dovrei usare indici separati per geolat e geolng quando eseguirò SEMPRE una query che includa entrambe le colonne
Teddy

1
No. Quando si incontra un "intervallo" (come con BETWEEN), non vengono considerati altri campi dell'indice! Quindi l'indice composito non è migliore.
Rick James,

19

Potrebbe esserci un malinteso su ciò che fa l'indice composito. Molte persone pensano che l'indice composito possa essere utilizzato per ottimizzare una query di ricerca purché la whereclausola copra le colonne indicizzate, nel tuo caso geolate geolng. Approfondiamo:

Credo che i tuoi dati sulle coordinate delle case siano decimali casuali in quanto tali:

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
...

Poiché geolate geolngvalori difficilmente si ripetono. Un indice composito attivo geolate geolngsimile a questo:

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
...

Pertanto la seconda colonna dell'indice composito è sostanzialmente inutile ! La velocità della tua query con un indice composito sarà probabilmente simile a un indice solo sulla geolatcolonna.

Come accennato da Will, MySQL fornisce supporto per l' estensione spaziale . Un punto spaziale è memorizzato in una singola colonna anziché in due lat lngcolonne separate . L'indice spaziale può essere applicato a tale colonna. Tuttavia, l'efficienza potrebbe essere sopravvalutata in base alla mia esperienza personale. Potrebbe essere che l'indice spaziale non risolva il problema bidimensionale ma acceleri semplicemente la ricerca usando R-Trees con divisione quadratica .

Il compromesso è che un punto spaziale consuma molta più memoria poiché utilizzava numeri a doppia precisione a otto byte per memorizzare le coordinate. Correggimi se sbaglio.


5

Gli indici compositi sono molto potenti in quanto:

  • Applicare l'integrità della struttura
  • Abilita l'ordinamento su un ID FILTRATO

ESEGUIRE L'INTEGRITÀ DELLA STRUTTURA

Gli indici compositi non sono solo un altro tipo di indice; possono fornire la struttura NECESSARIA a una tabella applicando l'integrità come chiave primaria.

Mysql's Innodb supporta il clustering e il seguente esempio illustra perché potrebbe essere necessario un indice composito.

Per creare un gruppo di amici tavoli (vale a dire per una rete sociale) abbiamo bisogno di 2 colonne: user_id, friend_id.

Table Strcture

user_id (medium_int)
friend_id (medium_int)

Primary Key -> (user_id, friend_id)

In sostanza, una chiave primaria (PK) è unica e, creando un PK composito, Innodb verificherà automaticamente che non vi siano duplicati su user_id, friend_idquando viene aggiunto un nuovo record. Questo è il comportamento previsto poiché nessun utente dovrebbe avere più di 1 record (collegamento di relazione) con friend_id = 2per esempio.

Senza un PK composito, possiamo creare questo schema usando una chiave surrogata:

user_friend_id
user_id
friend_id

Primary Key -> (user_friend_id)

Ora, ogni volta che viene aggiunto un nuovo record, dovremo verificare che user_id, friend_idnon esista già un record precedente con la combinazione .

Pertanto, un indice composito può imporre l'integrità della struttura.

ABILITARE L'ORDINE SU UN ID FILTRATO

È molto comune ordinare un set di record in base all'ora del post (data / ora o data / ora). Di solito, questo significa pubblicare su un determinato ID. Ecco un esempio

Tabella User_Wall_Posts (pensa se i post sulla bacheca di Facebook)

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

Primary Key -> (user_id, timestamp, author_id)

Vogliamo interrogare e trovare tutti i post per user_id = 10e ordinare i post dei commenti per timestamp(data).

QUERY SQL

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

Il PK composito consente a Mysql di filtrare e ordinare i risultati usando l'indice; Mysql non dovrà utilizzare un file temporaneo o un fileort per recuperare i risultati. Senza una chiave composita, ciò non sarebbe possibile e causerebbe una query molto inefficiente.

Come tale, le chiavi composite sono molto potenti e si adattano più al semplice problema di "Voglio cercare, column_a, column_bquindi userò le chiavi composite. Per il mio attuale schema di database, ho tante chiavi composte quante chiavi singole. Non trascurare l'uso di una chiave composita!


5

Gli indici compositi sono utili per

  • 0 o più clausole "=", più
  • al massimo una clausola di intervallo.

Un indice composito non può gestire due intervalli. Ne discuto ulteriormente nel mio libro di cucina indice .

Trova il più vicino : se la domanda riguarda davvero l' ottimizzazione

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

quindi nessun indice può davvero gestire entrambe le dimensioni.

Invece, bisogna "pensare fuori dagli schemi". Se una dimensione viene implementata tramite il partizionamento e l'altra viene implementata selezionando attentamente PRIMARY KEY, si può ottenere un'efficienza significativamente migliore per tabelle molto grandi di ricerca lat / lng. Il mio blog di latlng approfondisce come implementare "trova il più vicino" sul globo. Include il codice.

La PARTITIONssono strisce di intervalli di latitudine. L' PRIMARY KEYinizio inizia deliberatamente con la longitudine in modo che le righe utili siano probabilmente nello stesso blocco. Una routine memorizzata orchestra il codice disordinato per fare order by... limit...e per far crescere la "piazza" attorno al bersaglio fino a quando non si hanno abbastanza negozi di caffè (o altro). Si occupa anche dei calcoli del grande cerchio e della gestione della linea dati e dei poli.

Di Più

Ho scritto un altro blog; confronta 5 modi di fare ricerche lat / lng: http://mysql.rjweb.org/doc.php/latlng#representation_choices (Fa riferimento al link indicato sopra come uno dei 5.) Uno degli altri modi è questo, e sottolinea che sono ottimali per il caso particolare :

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

Cioè, avere entrambe le colonne in due indici e non avere indici a colonna singola su geolat e geolng è importante.


1

Non esiste un bianco e nero, una taglia adatta a tutte le risposte.

È necessario utilizzare un indice composito, quando il carico di lavoro della query trarrebbe vantaggio da uno.

Per determinare ciò è necessario profilare il carico di lavoro delle query.

Un indice composito entra in gioco quando le query possono essere soddisfatte interamente da quell'indice.

AGGIORNAMENTO (in risposta alla modifica della domanda postata): se si seleziona * dalla tabella, è possibile utilizzare l'indice composito, ma non è possibile. Sarà necessario eseguire EXPLAIN PLAN per essere sicuri.


Ha senso utilizzare un indice composito per i dati di geolocalizzazione (latitudine e longitudine)?
Teddy

1
Dipende interamente da quali query vengono eseguite su quella tabella.
Mitch Wheat,

Ho aggiornato il mio post originale per includere la query più comune eseguita. Vedi sopra.
Teddy

1

Per effettuare ricerche spaziali, è necessario un algoritmo R-Tree , che consente di cercare aree geografiche molto rapidamente. Esattamente quello che ti serve per questo lavoro.

Alcuni database hanno indici spaziali integrati. Una rapida ricerca su Google mostra che MySQL 5 li ha (che guardando il tuo SQL immagino tu stia usando MySQL).


1

L'indice composito può essere utile quando si desidera ottimizzare la group byclausola (consultare questo articolo http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html ). Per favore presta attenzione:

I presupposti più importanti per l'utilizzo degli indici per GROUP BY sono che tutte le colonne GROUP BY fanno riferimento agli attributi dello stesso indice e che l'indice memorizza le sue chiavi in ​​ordine (ad esempio, questo è un indice BTREE e non un indice HASH)


GROUP BYnon è stato menzionato.
Rick James,

Non è stato menzionato dove? :) È ovviamente menzionato nell'articolo a cui mi riferivo. E risponde alle domande poste: quando dovrei usare un indice composito in un database? Quali sono le conseguenze delle prestazioni utilizzando un indice composito)? Perché dovrei usare un indice composito?
Alexander,

Correzione: GROUP BYnon è stato menzionato dal PO.
Rick James,

Certo, questa era la risposta - uno dei casi in cui avremmo usato un indice composito in un database.
Alexander

0

Sono con @Mitch, dipende interamente dalle tue domande. Fortunatamente puoi creare e rilasciare indici in qualsiasi momento e puoi anteporre la parola chiave EXPLAIN alle tue query per vedere se l'analizzatore di query utilizza gli indici.

Se stai cercando una coppia lat / long esatta questo indice avrebbe probabilmente senso. Ma probabilmente cercherai case a una certa distanza da un determinato luogo, quindi le tue domande saranno simili a queste (vedi fonte ):

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

e l'indice molto probabilmente non sarà affatto utile. Per le query geospaziali, è necessario qualcosa di simile a questo .

Aggiornamento: con questa query:

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

L'analizzatore di query potrebbe utilizzare un indice su geolat da solo, o un indice su geolng da solo, o possibilmente su entrambi gli indici. Non penso che userebbe un indice composito. Ma è facile provare ognuna di queste permutazioni su un set di dati reale e quindi (a) vedere cosa ti dice EXPLAIN e (b) misurare il tempo impiegato dalla query.


Sto semplicemente usando il voler restituire case all'interno di una griglia quadrata. Conosco lo spazio, quindi non sto cercando di calcolare la distanza. Voglio semplicemente tornare a casa all'interno della griglia quadrata e voglio che si esibisca rapidamente. Come tale, voglio assicurarmi di avere i miei indici impostati correttamente. Questo aiuta?
Teddy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.