Optimisation des requêtes MySQL : un guide pratique et didactique
Les requêtes de base de données lentes peuvent constituer un goulot d'étranglement important pour toute application, entraînant une mauvaise expérience utilisateur et une augmentation des coûts d'infrastructure. Heureusement, MySQL fournit des outils puissants pour diagnostiquer et résoudre ces problèmes de performance. Ce guide vous expliquera les techniques essentielles pour optimiser vos requêtes MySQL, en se concentrant sur l'application pratique et la compréhension claire.
Nous verrons comment utiliser l'instruction EXPLAIN pour comprendre les plans d'exécution des requêtes, identifier les pièges courants en matière de performance et proposer des stratégies pour réécrire les requêtes inefficaces. En maîtrisant ces techniques, vous pouvez améliorer considérablement la réactivité de votre base de données et la performance globale de votre application.
Comprendre la performance des requêtes
Avant de plonger dans l'optimisation, il est crucial de comprendre pourquoi les requêtes peuvent être lentes. Les coupables courants incluent :
- Index manquants ou inefficaces : Sans index appropriés, MySQL doit effectuer des balayages complets de table (full table scans), ce qui est très inefficace pour les grandes tables.
- SQL mal écrit : Les sous-requêtes complexes, l'utilisation de
SELECT *et des conditions de jointure inefficaces peuvent toutes dégrader les performances. - Ensembles de données volumineux : Le simple fait de traiter de grandes quantités de données peut naturellement ralentir les opérations.
- Matériel et configuration : Une configuration de serveur sous-optimale ou des ressources matérielles insuffisantes peuvent également jouer un rôle, bien que ce guide se concentre sur l'optimisation au niveau des requêtes.
La puissance de EXPLAIN
L'instruction EXPLAIN est votre principal outil pour comprendre comment MySQL exécute une requête. Elle fournit des informations sur le plan d'exécution, montrant comment les tables sont jointes, quels index sont utilisés et comment les lignes sont balayées. Elle n'exécute pas réellement la requête, ce qui la rend sûre à utiliser sur les systèmes de production.
Comment utiliser EXPLAIN
Ajoutez simplement EXPLAIN au début de votre instruction SELECT, INSERT, DELETE, UPDATE ou REPLACE :
EXPLAIN SELECT * FROM users WHERE username = 'john_doe';
Interprétation du résultat de EXPLAIN
Le résultat de EXPLAIN est un tableau contenant plusieurs colonnes importantes :
id: Le numéro de séquence de la requête SELECT au sein de la requête globale. Les nombres plus élevés sont généralement exécutés en premier.select_type: Le type de SELECT (par exemple,SIMPLE,PRIMARY,SUBQUERY,DERIVED).table: La table consultée.partitions: Les partitions utilisées (si le partitionnement est activé).type: Le type de jointure. C'est l'une des colonnes les plus cruciales. Visezconst,eq_ref,ref,range. Évitezindexet surtoutALL(balayage complet de table).possible_keys: Montre les index que MySQL pourrait utiliser.key: L'index que MySQL a réellement choisi d'utiliser.key_len: La longueur de la clé choisie. Plus courte est généralement mieux.ref: La colonne ou la constante comparée à l'index (key).rows: Une estimation du nombre de lignes que MySQL doit examiner pour exécuter la requête.filtered: Le pourcentage de lignes filtrées par la condition de la table.Extra: Contient des informations supplémentaires sur la façon dont MySQL résout la requête. Les valeurs clés à surveiller incluent :Using where: Indique qu'une clauseWHEREest utilisée pour filtrer les lignes après les avoir récupérées.Using index: Signifie que la requête est couverte par un index (toutes les colonnes requises sont dans l'index), ce qui est bon.Using temporary: MySQL doit créer une table temporaire, souvent pour les opérationsGROUP BYouORDER BY. Cela peut être lent.Using filesort: MySQL doit effectuer un tri externe (n'utilisant pas d'index pour l'ordonnancement). C'est souvent le signe d'une clauseORDER BYinefficace.
Identifier les goulots d'étranglement avec EXPLAIN
Examinons quelques scénarios courants et comment EXPLAIN aide à identifier les problèmes :
Scénario 1 : Balayage complet de table (Full Table Scan)
Considérez une requête comme :
SELECT * FROM orders WHERE order_date = '2023-10-26';
Si la colonne order_date n'est pas indexée, EXPLAIN pourrait afficher :
+----+-------------+--------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+------+---------+------+---------+-------------+
| 1 | SIMPLE | orders | ALL | NULL | NULL | NULL | NULL | 1000000 | Using where |
+----+-------------+--------+------+---------------+------+---------+------+---------+-------------+
Problème : type: ALL indique un balayage complet de table. rows: 1000000 montre que MySQL doit examiner chaque ligne de la table orders. key: NULL signifie qu'aucun index n'a été utilisé.
Solution : Ajoutez un index à la colonne order_date :
CREATE INDEX idx_order_date ON orders (order_date);
Après avoir ajouté l'index, réexécutez EXPLAIN. Vous devriez maintenant voir un type beaucoup plus efficace (comme ref ou range) et un nombre de rows nettement inférieur.
Scénario 2 : ORDER BY ou GROUP BY inefficaces
SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id ORDER BY customer_id;
Si customer_id n'est pas indexé ou si l'index ne prend pas en charge l'ordonnancement, EXPLAIN pourrait afficher :
+----+-------------+--------+-------+---------------+------+---------+------+--------+----------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------+------+---------+------+--------+----------------------------------+
| 1 | SIMPLE | orders | index | NULL | NULL | NULL | NULL | 100000 | Using temporary; Using filesort |
+----+-------------+--------+-------+---------------+------+---------+------+--------+----------------------------------+
Problème : Using temporary et Using filesort indiquent que MySQL effectue des opérations coûteuses pour trier et regrouper les données. C'est souvent parce qu'aucun index ne peut satisfaire efficacement à la fois les exigences de regroupement et d'ordonnancement.
Solution : Selon la requête, la création d'un index qui couvre à la fois les colonnes de regroupement et d'ordonnancement peut aider. Pour cette requête spécifique, un index sur (customer_id) pourrait suffire. Si la requête était plus complexe, un index composite pourrait être nécessaire.
CREATE INDEX idx_customer_id ON orders (customer_id);
Scénario 3 : Utilisation inutile de SELECT *
Lorsque vous sélectionnez toutes les colonnes (*) mais que vous n'en avez besoin que de quelques-unes, vous pourriez empêcher MySQL d'utiliser un index pour couvrir la requête, même si un index existe sur les colonnes de la clause WHERE. Cela entraîne une recherche de table supplémentaire.
-- Assume an index on 'status'
SELECT * FROM tasks WHERE status = 'pending';
EXPLAIN pourrait afficher Using where, mais si la requête nécessite des colonnes qui ne sont pas dans l'index utilisé pour le filtrage, elle devra toujours accéder aux données de la table.
Solution : Spécifiez uniquement les colonnes dont vous avez besoin :
SELECT task_id, description FROM tasks WHERE status = 'pending';
Si vous interrogez fréquemment des colonnes spécifiques avec d'autres, envisagez de créer un index couvrant (covering index) qui inclut toutes les colonnes nécessaires à la requête.
Réécriture des requêtes lentes
Au-delà de l'indexation, la façon dont vous structurez votre SQL peut avoir un impact considérable sur les performances.
Éviter les sous-requêtes corrélées
Les sous-requêtes corrélées s'exécutent une fois pour chaque ligne traitée par la requête externe. Elles sont souvent inefficaces.
Inefficace :
SELECT o.order_id, o.order_date
FROM orders o
WHERE o.customer_id IN (
SELECT c.customer_id
FROM customers c
WHERE c.country = 'USA'
);
Efficace (utilisation de JOIN) :
SELECT o.order_id, o.order_date
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE c.country = 'USA';
L'utilisation de EXPLAIN sur les deux versions mettra en évidence la différence de performance.
Optimiser les clauses LIKE
Les jokers de tête (%) dans les clauses LIKE empêchent l'utilisation d'index.
Inefficace :
SELECT * FROM products WHERE product_name LIKE '%widget';
Meilleur (si possible) :
SELECT * FROM products WHERE product_name LIKE 'widget%';
Si vous avez absolument besoin de jokers de tête, envisagez l'indexation plein texte (full-text indexing) ou des solutions de recherche alternatives.
Utiliser UNION ALL au lieu de UNION lorsque c'est possible
UNION supprime les lignes dupliquées, ce qui nécessite une étape supplémentaire de tri et de déduplication. Si vous savez qu'il n'y a pas de doublons ou si vous n'avez pas besoin de les supprimer, UNION ALL est plus rapide.
Lent :
SELECT name FROM table1
UNION
SELECT name FROM table2;
Rapide :
SELECT name FROM table1
UNION ALL
SELECT name FROM table2;
Autres conseils d'optimisation
- Maintenir les statistiques à jour : Assurez-vous que les statistiques de table sont à jour afin que l'optimiseur de requêtes puisse prendre des décisions éclairées. Ceci est souvent géré automatiquement mais peut être mis à jour manuellement avec
ANALYZE TABLE. - Configuration du serveur : Bien que ce guide se concentre sur les requêtes, l'examen des variables de configuration MySQL telles que
innodb_buffer_pool_size,query_cache_size(déprécié dans MySQL 8.0) etsort_buffer_sizeest crucial pour la performance globale. - Surveillance régulière : Utilisez des outils comme MySQL Enterprise Monitor, Percona Monitoring and Management (PMM) ou les vues de schéma de performance intégrées pour suivre les requêtes lentes et identifier les tendances.
Conclusion
L'optimisation des requêtes MySQL est un processus itératif qui combine la compréhension de vos données, l'utilisation d'outils de diagnostic comme EXPLAIN et l'application des meilleures pratiques pour l'écriture SQL. En vous concentrant sur l'indexation, en évitant les balayages complets de table et en structurant vos requêtes efficacement, vous pouvez améliorer considérablement les performances et l'évolutivité de votre application. N'oubliez pas de toujours tester vos changements et de mesurer leur impact.
Bonne optimisation !