È davvero necessario che tutte le colonne selezionate vengano indicizzate affinché MySQL scelga di utilizzare l'indice?
Questa è una domanda caricata perché ci sono fattori che determinano se vale la pena usare un indice.
FATTORE # 1
Per ogni dato indice, qual è la popolazione chiave? In altre parole, qual è la cardinalità (conteggio distinto) di tutte le tuple registrate nell'indice?
FATTORE # 2
Quale motore di archiviazione stai usando? Tutte le colonne necessarie sono accessibili da un indice?
QUAL È IL PROSSIMO ???
Facciamo un semplice esempio: una tabella che contiene due valori (maschio e femmina)
Lasciate creare una tabella di questo tipo con un test per l'utilizzo dell'indice
USE test
DROP TABLE IF EXISTS mf;
CREATE TABLE mf
(
id int not null auto_increment,
gender char(1),
primary key (id),
key (gender)
) ENGINE=InnODB;
INSERT INTO mf (gender) VALUES
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
ANALYZE TABLE mf;
EXPLAIN SELECT gender FROM mf WHERE gender='F';
EXPLAIN SELECT gender FROM mf WHERE gender='M';
EXPLAIN SELECT id FROM mf WHERE gender='F';
EXPLAIN SELECT id FROM mf WHERE gender='M';
TEST InnoDB
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.07 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.06 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql>
TEST MyISAM
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.00 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 36 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | mf | ALL | gender | NULL | NULL | NULL | 40 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql>
Analisi per InnoDB
Quando i dati sono stati caricati come InnoDB, si noti che tutti e quattro i EXPLAIN
piani hanno utilizzato l' gender
indice. Il terzo e il quarto EXPLAIN
piano utilizzavano l' gender
indice anche se i dati richiesti erano id
. Perché? Perché id
è in PRIMARY KEY
e tutti gli indici secondari hanno puntatori di riferimento al PRIMARY KEY
(tramite il gen_clust_index ).
Analisi per MyISAM
Quando i dati sono stati caricati come MyISAM, si noti che i primi tre EXPLAIN
piani hanno utilizzato l' gender
indice. Nel quartoEXPLAIN
piano, lo Strumento per ottimizzare le query ha deciso di non utilizzare affatto un indice. Invece ha optato per una scansione completa della tabella. Perché?
Indipendentemente dal DBMS, gli ottimizzatori di query funzionano secondo una regola empirica molto semplice: se un indice viene selezionato come candidato da utilizzare per eseguire la ricerca e lo Strumento per ottimizzare la query calcola che deve cercare oltre il 5% del numero totale di righe nella tabella:
- viene eseguita una scansione completa dell'indice se tutte le colonne necessarie per il recupero si trovano nell'indice selezionato
- una scansione completa della tabella altrimenti
CONCLUSIONE
Se non si dispone di indici di copertura adeguati o se la popolazione chiave per una data tupla è superiore al 5% della tabella, devono accadere sei cose:
- Vieni a capire che devi profilare le query
- Trova tutte
WHERE
, GROUP BY
e ORDER BY` clausole da quelle query
- Formulare gli indici in questo ordine
WHERE
colonne della clausola con valori statici
GROUP BY
colonne
ORDER BY
colonne
- Evita scansioni di tabelle complete (query prive di una
WHERE
clausola ragionevole )
- Evita popolazioni chiave sbagliate (o almeno memorizza nella cache quelle popolazioni chiave sbagliate)
- Decidi il miglior motore di archiviazione MySQL ( InnoDB o MyISAM ) per le tabelle
Ho scritto su questa regola empirica del 5% in passato:
AGGIORNAMENTO 2012-11-14 13:05 EDT
Ho dato uno sguardo alla tua domanda e al post SO originale . Poi, ho pensato al mio che Analysis for InnoDB
ho menzionato prima. Coincide con il person
tavolo. Perché?
Per entrambi i tavoli mf
eperson
- Storage Engine è InnoDB
- La chiave primaria è
id
- L'accesso alla tabella è per indice secondario
- Se la tabella fosse MyISAM, vedremmo un
EXPLAIN
piano completamente diverso
Ora, guardate la query dalla questione SO: select * from person order by age\G
. Poiché non esiste una WHERE
clausola, è stata esplicitamente richiesta una scansione completa della tabella . L'ordinamento predefinito della tabella sarebbe per id
(PRIMARY KEY) a causa del suo auto_increment e gen_clust_index (aka Clustered Index) è ordinato da rowid interno . Quando hai ordinato l'indice, tieni presente che gli indici secondari InnoDB hanno il rowid associato a ciascuna voce di indice. Ciò produce ogni volta la necessità interna di accedere alla riga completa.
L'impostazione ORDER BY
su una tabella InnoDB può essere un'attività piuttosto scoraggiante se si ignorano questi fatti su come sono organizzati gli indici InnoDB.
Tornando a quella query SO, poiché hai esplicitamente richiesto una scansione completa della tabella , IMHO lo MySQL Query Optimizer ha fatto la cosa giusta (o almeno, ha scelto il percorso di minor resistenza). Quando si tratta di InnoDB e della query SO, è molto più semplice eseguire una scansione completa della tabella e poi alcunifilesort
piuttosto che eseguire una scansione completa dell'indice e una ricerca di riga tramite gen_clust_index per ogni voce di indice secondaria.
Non sono un sostenitore dell'utilizzo dei suggerimenti sull'indice perché ignora il piano EXPLAIN. Tuttavia, se conosci davvero i tuoi dati meglio di InnoDB, dovrai ricorrere a Suggerimenti per l'indice, in particolare con query che non hanno WHERE
clausole.
AGGIORNAMENTO 2012-11-14 14:21 EDT
Secondo il libro Understanding MySQL Internals
Pagina 202 Il paragrafo 7 dice quanto segue:
I dati sono archiviati in una struttura speciale chiamata indice cluster , che è un albero B con la chiave primaria che funge da valore della chiave e il record effettivo (anziché un puntatore) nella parte di dati. Pertanto, ogni tabella InnoDB deve avere una chiave primaria. Se non ne viene fornito uno, viene aggiunta una colonna ID riga speciale normalmente non visibile all'utente per fungere da chiave primaria. Una chiave secondaria memorizzerà il valore della chiave primaria che identifica il record. Il codice B-tree si trova in innobase / btr / btr0btr.c .
Questo è il motivo per cui ho affermato in precedenza: è molto più semplice eseguire una scansione completa della tabella e quindi alcuni fileort piuttosto che eseguire una scansione completa dell'indice e una ricerca di riga tramite gen_clust_index per ogni voce di indice secondaria . InnoDB eseguirà una doppia ricerca dell'indice ogni volta . Sembra un po 'brutale, ma sono solo i fatti. Ancora una volta, prendere in considerazione la mancanza di WHERE
clausola. Questo, di per sé, è il suggerimento di MySQL Query Optimizer per eseguire una scansione completa della tabella.
FOR ORDER BY
(che è il caso specifico in questa domanda). La domanda affermava che in questo caso il motore di archiviazione eraInnoDB
(e la domanda SO originale mostra che le 10k righe sono distribuite in modo abbastanza uniforme su 8 elementi, anche la cardinalità non dovrebbe essere un problema). Purtroppo, non penso che questo risponda alla domanda.