7.2 Optimisation des commandes SELECT et autres requêtes
7 Optimisation de MySQL
Manuel de Référence MySQL 4.1 : Version Française
->Syntaxe de EXPLAIN (Obtenir des informations sur les SELECT ) . Mesurer les performances d'une requête . Vitesse des requêtes SELECT . Comment MySQL optimise les clauses WHERE . Comment MySQL optimise la condition OR . Comment MySQL optimise IS NULL . Comment MySQL optimise DISTINCT . Comment MySQL optimise les clauses LEFT JOIN et RIGHT JOIN . Comment MySQL optimise ORDER BY . Comment MySQL optimise LIMIT . Comment éviter les scans de tables,,, . Vitesse des requêtes INSERT . Vitesses des commandes UPDATE . Rapidité des requêtes DELETE . Autres conseils d'optimisation
|
7.2.1 Syntaxe de EXPLAIN (Obtenir des informations sur les SELECT )
Ou :
EXPLAIN SELECT select_options
|
EXPLAIN nom_de_table
est un synonyme de
DESCRIBE nom_de_table
ou
SHOW COLUMNS FROM nom_de_table
.
-
La syntaxe
EXPLAIN tbl_name
est synonyme de
DESCRIBE tbl_name
ou
SHOW COLUMNS FROM tbl_name
.
-
Lorsque vous faites précéder une commande
SELECT
avec le mot clé
EXPLAIN
,
MySQL vous explique comment il va traiter la commande
SELECT
,
choisir les tables et index pour les jointures.
Cette section fournit des informations sur comment utiliser
EXPLAIN
.
Avec l'aide de
EXPLAIN
, vous pouvez identifier les index à ajouter
pour accélérer les commandes
SELECT
.
Vous devriez souvent utiliser la commande
ANALYZE TABLE
pour mettre à jour
les statistiques de cardinalité de vos tables, qui affectent les choix
de l'optimiseur. Syntaxe de
ANALYZE TABLE
.
Vous pouvez aussi voir si l'optimiseur fait les jointures dans un
ordre vraiment optimal. Pour forcer l'optimiseur à utiliser un ordre
spécifique de jointure dans une commande
SELECT
, ajoutez
l'attribut
STRAIGHT_JOIN
à la clause.
Pour les jointures complexes,
EXPLAIN
retourne une ligne d'information
pour chaque table utilisée dans la commande
SELECT
. Les tables sont
listées dans l'ordre dans lequel elles seront lues. MySQL résout toutes les
jointures avec une seule passe multi-jointure. Cela signifie que MySQL
lit une ligne dans la première table, puis recherche les lignes qui correspondent
dans la seconde, puis dans la troisième, etc. Lorsque toutes les tables ont été
traitées, MySQL affiche les colonnes demandées, et il remonte dans les tables
jusqu'à la dernière qui avait encore des lignes à traiter. La prochaine ligne est
alors traitée de la même façon.
Avec MySQL version 4.1 l'affichage de
EXPLAIN
a été modifié pour mieux
fonctionner avec les structures comme
UNION
, sous-requêtes, et tables dérivées.
La plus importante évolution est l'addition de deux nouvelles colonnes :
id
et
select_type
.
Le résultat de la commande
EXPLAIN
est constitué des colonnes suivantes :
-
id
-
identifiant de
SELECT
, le numéro séquentiel de cette commande
SELECT
dans la requête.
-
select_type
-
Type de clause
SELECT
, qui peut être :
-
SIMPLE
-
SELECT
simple (sans utiliser de clause
UNION
ou de sous-requêtes).
-
PRIMARY
-
SELECT
extérieur.
-
UNION
-
Second et autres
UNION
SELECT
s.
-
DEPENDENT UNION
-
Second et autres
UNION
SELECTS
s, dépend de la commande
extérieure.
-
SUBQUERY
-
Premier
SELECT
de la sous-requête.
-
DEPENDENT SUBSELECT
-
Premier
SELECT
, dépendant de la requête extérieure.
-
DERIVED
-
Table dérivée
SELECT
.
-
table
-
La table à laquelle la ligne fait référence.
-
type
-
Le type de jointure. Les différents types de jointures sont les suivants,
dans l'ordre du plus efficace au plus lent :
-
system
-
La table a une seule ligne (c'est une table système). C'est un cas spécial
du type de jointure
const
.
-
const
-
La table a au plus une ligne correspondante, qui sera lue dès le début de la
requête. Comme il n'y a qu'une seule ligne, les valeurs des colonnes de cette
ligne peuvent être considérées comme des constantes pour le reste de l'optimiseur.
Les tables
const
sont très rapides, car elles ne sont lues qu'une fois.
const
est utilisé lorsque vous comparez toutes les parties
d'une clé
PRIMARY
/
UNIQUE
avec des constantes :
SELECT * FROM const_table WHERE primary_key=1; SELECT * FROM const_table WHERE primary_key_part1=1 AND primary_key_part2=2;
|
-
eq_ref
-
Une ligne de cette table sera lue pour chaque combinaison de ligne des tables
précédentes. C'est le meilleur type de jointure possible, à l'exception
des précédents. Il est utilisé lorsque toutes les parties d'un index sont
utilisées par la jointure, et que l'index est
UNIQUE
ou
PRIMARY KEY
.
eq_ref
peut être utilisé pour les colonnes indexées, qui sont comparées
avec l'opérateur
=
. L'élément comparé doit être une constante ou une
expression qui utiliser les colonnes de la table qui est avant cette table.
Dans l'exemple suivant,
ref_table
sera capable d'utiliser
eq_ref
:
SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column; SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
|
-
ref
-
Toutes les lignes avec des valeurs d'index correspondantes seront lues dans
cette table, pour chaque combinaison des lignes précédentes.
ref
est utilisé
si la jointure n'utilise que le préfixe de gauche de la clé, ou si la clé n'est pas
UNIQUE
ou
PRIMARY KEY
(en d'autres termes, si la jointure ne peut
pas sélectionner qu'une seule ligne en fonction de la clé). Si la clé qui est utilisée
n'identifie que quelques lignes à chaque fois, la jointure est bonne.
ref
peut être utilisé pour les colonnes indexées, qui sont comparées avec
l'opérateur
=
.
Dans les exemples suivants,
ref_table
sera capable d'utiliser
ref
.
SELECT * FROM ref_table WHERE key_column=expr; SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column; SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
|
-
ref_or_null
-
Comme
ref
, mais avec le coût supplémentaire pour les recherches couvrant
les valeurs
NULL
. Ce type de jointure est nouveau en MySQL 4.1.1 est sert essentiellement
à la résolution des sous-requêtes.Dans les exemples suivants, MySQL peut utiliser une jointure
ref_or_null
pour traiter
ref_table
:
SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;
|
Comment MySQL optimise les clauses
IS NULL
.
-
index_merge
-
Ce type de jointure indique que l'optimisation de type
Index Merge
est
utilisée. Dans ce cas, la colonne
key
contient une liste d'index utilisés,
et
key_len
contient la liste des plus longues parties de clés utilisées.
Pour plus d'informations, voyez Comment MySQL optimise les clauses
OR
.
-
unique_subquery
-
Ce type remplace le type
ref
dans certaines sous-requêtes
IN
de la forme
suivante :
value IN (SELECT primary_key FROM single_table WHERE some_expr)
|
unique_subquery
est simplement une analyse d'index, qui remplace
complètement la sous-requête pour une meilleure efficacité.
-
index_subquery
-
Ce type de jointure est similaire à
unique_subquery
. Elle remplace des sous-requêtes
IN
, mais elle fonctionne pour les index non-uniques dans les sous-requêtes
de la forme suivante :
value IN (SELECT key_column FROM single_table WHERE some_expr)
|
-
range
-
Seules les lignes qui sont dans un intervalle donné seront lues, en utilisant
l'index pour sélectionner les lignes. La colonne
key
indique quel est
l'index utilisé.
key_len
contient la taille de la partie de la clé qui est
utilisée. La colonne
ref
contiendra la valeur
NULL
pour ce type.
range
peut être utilisé lorsqu'une colonne indexée est comparée avec
une constante comme
=
,
<>
,
>
,
>=
,
<
,
<=
,
IS NULL
,
<=>
,
BETWEEN
ou
IN
.
SELECT * FROM tbl_name WHERE key_column = 10; SELECT * FROM tbl_name WHERE key_column BETWEEN 10 and 20; SELECT * FROM tbl_name WHERE key_column IN (10,20,30); SELECT * FROM tbl_name WHERE key_part1= 10 AND key_part2 IN (10,20,30);
|
-
index
-
C'est la même chose que
ALL
, hormis le fait que seul l'arbre d'index
est étudié. C'est généralement plus rapide que
ALL
, car le fichier
d'index est plus petit que le fichier de données.
Cette méthode peut être utilisée lorsque la requête utilise une colonne qui
fait partie d'un index.
-
ALL
-
Une analyse complète de la table sera faîte pour chaque combinaison de lignes
issue des premières tables. Ce n'est pas bon si la première table n'est pas
une jointure de type
const
et c'est
très
mauvais dans les autres
cas. Normalement vous pouvez éviter ces situations de
ALL
en ajoutant
des index basée sur des parties de colonnes.
-
possible_keys
-
La colonne
possible_keys
indique quels index MySQL va pouvoir utiliser
pour trouver les lignes dans cette table. Notez que cette colonne est totalement
dépendante de l'ordre des tables. Cela signifie que certaines clés
de la colonne
possible_keys
pourraient ne pas être utilisées dans
d'autres cas d'ordre de tables.
Si cette colonne est vide, il n'y a pas d'index pertinent. Dans ce cas,
vous pourrez améliorer les performances en examinant votre clause
WHERE
pour voir si des colonnes sont susceptibles d'être indexée.
Si c'est le cas, créez un index approprié, et examinez le résultat avec
la commande
EXPLAIN
. Syntaxe de
ALTER TABLE
.
Pour connaître tous les index d'une table, utilisez le code
SHOW INDEX FROM nom_de_table
.
-
key
-
La colonne
key
indique l'index que MySQL va décider d'utiliser. Si la clé
vaut
NULL
, aucun index n'a été choisi. Pour forcer MySQL à
utiliser un index listé dans la colonne
possible_keys
, utilisez
USE KEY/IGNORE KEY
dans votre requête.
Syntaxe de
SELECT
.Pour les tables
MyISAM
et
BDB
, la commande
ANALYZE TABLE
va aider l'optimiseur à choisir les meilleurs index. Pour les tables
MyISAM
,
myisamchk --analyze
fera la même chose. Voyez Syntaxe de
ANALYZE TABLE
et Utilisation de
myisamchk
pour maintenir les tables et recouvrir les données .
-
key_len
-
La colonne
key_len
indique la taille de la clé que MySQL a décidé d'utiliser.
La taille est
NULL
si la colonne
key
vaut
NULL
. Notez que cela vous
indique combien de partie d'une clé multiple MySQL va réellement utiliser.
-
ref
-
La colonne
ref
indique quelle colonne ou quelles constantes sont utilisées
avec la clé
key
, pour sélectionner les lignes de la table.
-
rows
-
La colonne
rows
indique le nombre de ligne que MySQL estime devoir
examiner pour exécuter la requête.
-
Extra
-
Cette colonne contient des informations additionnelle sur comment
MySQL va résoudre la requête. Voici une explication des différentes
chaînes que vous pourriez trouver dans cette colonne :
-
Distinct
-
MySQL ne va pas continuer à chercher d'autres lignes que la ligne courante,
après en avoir trouvé une.
-
Not exists
-
MySQL a été capable d'appliquer une optimisation de type
LEFT JOIN
sur la requête, et ne va pas examiner d'autres lignes de cette table pour la
combinaison de lignes précédentes, une fois qu'il a trouvé une ligne qui satisfait
le critère de
LEFT JOIN
.
Voici un exemple de cela :
SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id WHERE t2.id IS NULL;
|
Supposons que
t2.id
est défini comme
NOT NULL
. Dans ce cas,
MySQL va scanner
t1
et rechercher des lignes dans
t2
via
t1.id
. Si MySQL trouve une ligne dans
t2
, il sait que
t2.id
ne peut pas être
NULL
, et il ne va pas scanner le reste des lignes de
t2
qui ont le même
id
. En d'autres termes, pour chaque ligne de
t1
, MySQL n'a besoin que de faire une recherche dans
t2
, indépendamment
du nombre de lignes qui sont trouvées dans
t2
.
-
range checked for each record (index map: #)
-
MySQL n'a pas trouvé d'index satisfaisant à utiliser. Il va, à la place, pour
chaque combinaison de lignes des tables précédentes, faire une vérification
de quel index utiliser (si il en existe), et utiliser cet index pour continuer
la recherche. Ce n'est pas très rapide, mais c'est plus rapide que de faire
une recherche sans aucun index.
-
Using filesort
-
MySQL va avoir besoin d'un autre passage pour lire les lignes dans l'ordre.
Le tri est fait en passant en revue toutes les lignes, suivant le
type de jointure
est stocker la clé de tri et le pointeur de la ligne pour
chaque ligne qui satisfont la clause
WHERE
. Alors, les clés sont triées.
Finalement, les lignes sont triées dans l'ordre.
-
Using index
-
Les informations de la colonne sont lues de la table, en utilisant uniquement
les informations contenues dans l'index, sans avoir à faire d'autres lectures.
Cela peut arriver lorsque toutes les colonnes utilisées dans une table font
partie de l'index.
-
Using temporary
-
Pour résoudre la requête, MySQL va avoir besoin de créer une table temporaire
pour contenir le résultat. C'est typiquement ce qui arrive si vous utilisez
une clause
ORDER BY
sur une colonne différente de celles qui font partie
de
GROUP BY
.
-
Using where
-
Une clause
WHERE
sera utilisée pour restreindre les lignes qui seront
trouvées dans la table suivante, ou envoyée au client. Si vous n'avez pas cette
information, et que la table est de type
ALL
ou
index
,
vous avez un problème dans votre requête (si vous ne vous attendiez pas à tester
toutes les lignes de la table).
Si vous voulez rendre vos requêtes aussi rapide que possible, vous devriez
examiner les lignes qui utilisent
Using filesort
et
Using temporary
.
Vous pouvez obtenir une bonne indication de la qualité de votre jointure en
multipliant toutes les valeurs de la colonne
rows
dans la table de
la commande
EXPLAIN
. Cela est une estimation du nombre de lignes
que MySQL va examiner pour exécuter cette requête. C'est aussi ce nombre
qui sera utilisé pour interrompre votre requête, grâce à la variable
max_join_size
.
Choix des paramètres du serveur .L'exemple ci-dessous illustre comme une requête
JOIN
peut être optimisée
avec les résultats de la commande
EXPLAIN
.
Supposons que vous avez la requête
SELECT
suivante, et que vous l'examinez
avec
EXPLAIN
:
EXPLAIN SELECT tt.TicketNumber, tt.TimeIn, tt.ProjectReference, tt.EstimatedShipDate, tt.ActualShipDate, tt.ClientID, tt.ServiceCodes, tt.RepetitiveID, tt.CurrentProcess, tt.CurrentDPPerson, tt.RecordVolume, tt.DPPrinted, et.COUNTRY, et_1.COUNTRY, do.CUSTNAME FROM tt, et, et AS et_1, do WHERE tt.SubmitTime IS NULL AND tt.ActualPC = et.EMPLOYID AND tt.AssignedPC = et_1.EMPLOYID AND tt.ClientID = do.CUSTNMBR;
|
Pour cette exemple, nous supposons que :
-
Les colonnes utilisées sont déclarées comme ceci :
Table
|
Colonne
|
Type de colonne
|
tt
|
ActualPC
|
CHAR(10)
|
tt
|
AssignedPC
|
CHAR(10)
|
tt
|
ClientID
|
CHAR(10)
|
et
|
EMPLOYID
|
CHAR(15)
|
do
|
CUSTNMBR
|
CHAR(15)
|
-
Les tables ont les index suivants :
Table
|
Index
|
tt
|
ActualPC
|
tt
|
AssignedPC
|
tt
|
ClientID
|
et
|
EMPLOYID
(clé primaire)
|
do
|
CUSTNMBR
(clé primaire)
|
-
Les valeurs de
tt.ActualPC
ne sont pas réparties également.
Initialement, avant toute optimisation, la commande
EXPLAIN
produit les informations suivantes :
table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 do ALL PRIMARY NULL NULL NULL 2135 et_1 ALL PRIMARY NULL NULL NULL 74 tt ALL AssignedPC, NULL NULL NULL 3872 ClientID, ActualPC range checked for each record (key map: 35)
|
Comme le type
type
vaut
ALL
pour chaque table, le résultat
indique que MySQL fait une analyse complète de toutes les tables. Cela va
prendre un très long temps de calcul, car le nombre de lignes à examiner
de cette façon est le produit du nombre de lignes de toutes les tables :
dans notre cas, cela vaut
74 * 2135 * 74 * 3872 =
45,268,558,720
lignes. Si les tables étaient plus grandes, cela serait encore
pire.Le premier problème que vous avons ici, est que MySQL ne peut pas (encore)
utiliser d'index sur les colonnes, si elles sont déclarées différemment.
Dans ce contexte, les colonnes
VARCHAR
et
CHAR
sont les mêmes,
mais elles ont été déclarée avec des tailles différentes. Comme
tt.ActualPC
est déclarée comme
CHAR(10)
et que
et.EMPLOYID
est déclaré comme
CHAR(15)
, il y a un problème
de taille.
Pour corriger cette disparité, utilisez la commande
ALTER TABLE
pour
agrandir la colonne
ActualPC
de 10 caractères à 15 :
mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);
|
Maintenant,
tt.ActualPC
et
et.EMPLOYID
sont tous les deux des colonnes
de type
VARCHAR(15)
. Exécuter la commande
EXPLAIN
produit maintenant
le résultat suivant :
table type possible_keys key key_len ref rows Extra tt ALL AssignedPC, NULL NULL NULL 3872 Using ClientID, where ActualPC do ALL PRIMARY NULL NULL NULL 2135 range checked for each record (key map: 1) et_1 ALL PRIMARY NULL NULL NULL 74 range checked for each record (key map: 1) et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
|
Ce n'est pas parfait, mais c'est bien mieux. Le produit de toutes les lignes
a été divisé par 74). Cette version s'exécute en quelques secondes.Une autre modification peut être faîte pour éliminer les problèmes de
taille de colonne pour
tt.AssignedPC = et_1.EMPLOYID
et
tt.ClientID = do.CUSTNMBR
:
mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15), -> MODIFY ClientID VARCHAR(15);
|
Maintenant,
EXPLAIN
produit le résultat suivant :
table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 tt ref AssignedPC, ActualPC 15 et.EMPLOYID 52 Using ClientID, where ActualPC et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
|
C'est presque aussi bon que cela pourrait l'être.
Le problème final est que, par défaut, MySQL supporte que les valeurs de la colonne
tt.ActualPC
sont uniformément répartie, et que ce n'est pas le cas
pour la table
tt
. Mais il est facile de le dire à MySQL :
mysql> <userinput>ANALYZE TABLE tt;</userinput>
|
Maintenant, la jointure est parfaite, et la commande
EXPLAIN
produit
ce résultat :
table type possible_keys key key_len ref rows Extra tt ALL AssignedPC NULL NULL NULL 3872 Using ClientID, where ActualPC et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1 et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
|
Notez que la colonne
rows
dans le résultat de
EXPLAIN
est une
prédiction éclairée de l'optimiseur de jointure MySQL. Pour optimiser une
requête, vous devriez vérifier si ces nombres sont proches de la réalité.
Si ce n'est pas le cas, vous pourriez obtenir de meilleures performances avec
l'attribut
STRAIGHT_JOIN
dans votre commande
SELECT
, et en
choisissant vous même l'ordre de jointure des tables dans la clause
FROM
.
|