Est-il en effet nécessaire que toutes les colonnes sélectionnées soient indexées pour que MySQL choisisse d'utiliser l'index?
Il s'agit d'une question délicate car il existe des facteurs qui déterminent si un indice mérite d'être utilisé.
FACTEUR # 1
Pour un indice donné, quelle est la population clé? En d'autres termes, quelle est la cardinalité (nombre distinct) de tous les tuples enregistrés dans l'index?
FACTEUR # 2
Quel moteur de stockage utilisez-vous? Toutes les colonnes nécessaires sont-elles accessibles à partir d'un index?
ET APRÈS ???
Prenons un exemple simple: un tableau qui contient deux valeurs (mâle et femelle)
Laissez créer une telle table avec un test d'utilisation de l'index
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>
Analyse pour InnoDB
Lorsque les données ont été chargées en tant qu'InnoDB, veuillez noter que les quatre EXPLAIN
plans ont utilisé l' gender
index. Les troisième et quatrième EXPLAIN
plans ont utilisé l' gender
indice même si les données demandées l'étaient id
. Pourquoi? Parce que se id
trouve dans le PRIMARY KEY
et tous les index secondaires ont des pointeurs de référence vers le PRIMARY KEY
(via le gen_clust_index ).
Analyse pour MyISAM
Lorsque les données ont été chargées en tant que MyISAM, veuillez noter que les trois premiers EXPLAIN
plans ont utilisé l' gender
index. Dans le quatrième EXPLAIN
plan, l'Optimiseur de requête a décidé de ne pas utiliser du tout d'index. Il a plutôt opté pour une analyse complète de la table. Pourquoi?
Quel que soit le SGBD, les optimiseurs de requête fonctionnent sur une règle empirique très simple: si un index est filtré en tant que candidat à utiliser pour effectuer la recherche et Query Optimizer calcule qu'il doit rechercher plus de 5% du nombre total de lignes du tableau:
- une analyse complète de l'index est effectuée si toutes les colonnes nécessaires à la récupération se trouvent dans l'index sélectionné
- une analyse complète de la table sinon
CONCLUSION
Si vous n'avez pas d'index de couverture appropriés ou si la population clé pour un tuple donné est supérieure à 5% du tableau, six choses doivent se produire:
- Venez à la réalisation que vous devez profiler les requêtes
- Trouver tous
WHERE
, GROUP BY
et l' ordre des clauses BY` de ces requêtes
- Formuler des index dans cet ordre
WHERE
colonnes de clause avec des valeurs statiques
GROUP BY
Colonnes
ORDER BY
Colonnes
- Évitez les analyses de table complètes (les requêtes manquant d'une
WHERE
clause raisonnable )
- Évitez les populations de clés incorrectes (ou du moins mettez en cache ces populations de clés incorrectes)
- Décidez du meilleur moteur de stockage MySQL ( InnoDB ou MyISAM ) pour les tables
J'ai écrit sur cette règle empirique de 5% dans le passé:
MISE À JOUR 2012-11-14 13:05 EDT
J'ai jeté un coup d'œil à votre question et au message SO original . Ensuite, j'ai pensé à mon que Analysis for InnoDB
j'ai mentionné auparavant. Il coïncide avec le person
tableau. Pourquoi?
Pour les tables mf
etperson
- Le moteur de stockage est InnoDB
- La clé primaire est
id
- L'accès à la table se fait par index secondaire
- Si la table était MyISAM, nous verrions un
EXPLAIN
plan complètement différent
Maintenant, regardez la requête de la question SO: select * from person order by age\G
. Puisqu'il n'y a aucune WHERE
clause, vous avez explicitement demandé une analyse complète de la table . L'ordre de tri par défaut de la table serait par id
(PRIMARY KEY) en raison de son auto_increment et le gen_clust_index (aka Clustered Index) est ordonné par rowid interne . Lorsque vous avez ordonné par l'index, gardez à l'esprit que les index secondaires InnoDB ont le rowid attaché à chaque entrée d'index. Cela crée le besoin interne d'un accès complet aux lignes à chaque fois.
La configuration ORDER BY
sur une table InnoDB peut être une tâche plutôt intimidante si vous ignorez ces faits sur la façon dont les index InnoDB sont organisés.
Pour en revenir à cette requête SO, puisque vous avez explicitement demandé une analyse complète de la table , à mon humble avis, MySQL Query Optimizer a fait la bonne chose (ou du moins, a choisi le chemin de moindre résistance). En ce qui concerne InnoDB et la requête SO, il est beaucoup plus facile d'effectuer une analyse complète de la table, puis certaines filesort
plutôt que de faire une analyse complète de l'index et une recherche de ligne via gen_clust_index pour chaque entrée d'index secondaire.
Je ne suis pas partisan de l'utilisation d'index car il ignore le plan EXPLAIN. Néanmoins, si vous connaissez vraiment mieux vos données qu'InnoDB, vous devrez recourir aux indices d'index, en particulier pour les requêtes sans WHERE
clause.
MISE À JOUR 2012-11-14 14:21 EDT
Selon le livre Understanding MySQL Internals
Le paragraphe 7 dit ce qui suit:
Les données sont stockées dans une structure spéciale appelée index clusterisé , qui est un arbre B avec la clé primaire agissant comme valeur de clé, et l'enregistrement réel (plutôt qu'un pointeur) dans la partie données. Ainsi, chaque table InnoDB doit avoir une clé primaire. Si aucun n'est fourni, une colonne d'ID de ligne spéciale qui n'est normalement pas visible par l'utilisateur est ajoutée pour agir comme clé primaire. Une clé secondaire stockera la valeur de la clé primaire qui identifie l'enregistrement. Le code de l'arbre B se trouve dans innobase / btr / btr0btr.c .
C'est pourquoi je l'ai dit plus tôt: il est beaucoup plus facile d'effectuer une analyse complète de la table, puis un tri de fichiers plutôt que d' effectuer une analyse complète de l'index et une recherche de ligne via gen_clust_index pour chaque entrée d'index secondaire . InnoDB va faire une recherche à double index à chaque fois . Cela semble plutôt brutal, mais ce ne sont que les faits. Encore une fois, prenez en considération l'absence de WHERE
clause. Ceci, en soi, est l'indice de l'optimiseur de requêtes MySQL pour effectuer une analyse complète de la table.
FOR ORDER BY
(ce qui est le cas spécifique dans cette question). La question indiquait que dans ce cas, le moteur de stockage étaitInnoDB
(et la question SO d'origine montre que les 10 000 lignes sont réparties assez uniformément sur 8 éléments, la cardinalité ne devrait pas être un problème ici non plus). Malheureusement, je ne pense pas que cela réponde à la question.