PolarDB for MySQL réécrit automatiquement les instructions SQL lors de la phase d'optimisation afin de supprimer les sous-requêtes redondantes et de pré-calculer celles qui sont constantes. Cette approche réduit la complexité du plan d'exécution et améliore les performances des requêtes, un problème fréquent lorsque les frameworks ORM (Object-Relational Mapping) génèrent des requêtes profondément imbriquées.
Périmètre
Série de produits : Cluster Edition et Standard Edition
Version du noyau : MySQL 8.0.2, révision 8.0.2.2.19 ou ultérieure
Activer l'optimisation des sous-requêtes
Contrôlez l'optimisation des sous-requêtes à l'aide du paramètre loose_simplify_subq_mode.
Le nom du paramètre varie selon l'endroit où vous le définissez :
Console PolarDB : Utilisez le préfixe
loose_(loose_simplify_subq_mode). Certains paramètres conservent ce préfixe pour assurer la compatibilité avec les fichiers de configuration MySQL.Session de base de données (ligne de commande ou client) : Supprimez le préfixe
loose_et utilisezSET simplify_subq_mode = '...'.
| Paramètre | Niveau | Description |
|---|---|---|
loose_simplify_subq_mode |
Global/Session | Contrôle l'optimisation des sous-requêtes. Valeurs valides : REPLICA_ON (par défaut) — active l'optimisation uniquement sur les nœuds en lecture seule (RO) ; ON — active la fonctionnalité ; OFF — désactive l'optimisation. |
Fonctionnement
L'optimiseur applique trois types de réécritures durant la phase d'optimisation :
Élimination des SELECT redondants — supprime les couches d'enveloppe SELECT inutiles autour des fonctions d'agrégation ou des expressions constantes.
Pré-évaluation de [NOT] EXISTS — remplace les sous-requêtes EXISTS par
TRUEouFALSElorsque le résultat peut être déterminé sans exécuter la sous-requête.ANY/ALL LIMIT injection — ajoute
LIMIT 1aux sous-requêtes à projection constante dans les clauses ANY ou ALL afin d'éviter les balayages complets de table.
Chaque scénario ci-dessous illustre la réécriture exacte appliquée par l'optimiseur.
Scénarios d'optimisation
Scénario 1 : Éliminer l'imbrication SELECT redondante
Cas d'application : La sous-requête enveloppe uniquement une fonction d'agrégation ou une expression, sans autre logique complexe.
Modèle de réécriture :
-- Before
SELECT (SELECT SUM(a) FROM t2) FROM dual;
-- After
SELECT SUM(`test`.`t2`.`a`) AS `sum(a)` FROM `test`.`t2`
La même élimination s'applique dans les clauses HAVING, où l'optimiseur aplatit plusieurs niveaux d'imbrication :
-- Before
SELECT SUM(a) FROM t2 HAVING (SELECT(SELECT(SELECT count(b))));
-- After
SELECT SUM(`testdb`.`t2`.`a`) AS `SUM(a)` from `testdb`.`t2` HAVING (0 <> count(`testdb`.`t2`.`b`))
Vérification par l'exemple :
-
Préparez les données de test :
DROP TABLE IF EXISTS t2; CREATE TABLE t2 ( id INT PRIMARY KEY AUTO_INCREMENT, a INT, b INT ); INSERT INTO t2 (a, b) VALUES (10, 100), (20, NULL), (50, 200), (120, NULL); -
Examinez le plan d'exécution sans optimisation :
SET simplify_subq_mode = 'OFF'; EXPLAIN SELECT SUM(a) FROM t2 HAVING (SELECT(SELECT(SELECT count(b)))); SHOW WARNINGS;Résultat — quatre niveaux d'imbrication SELECT subsistent :
/* select#1 */ select sum(`testdb`.`t2`.`a`) AS `SUM(a)` from `testdb`.`t2` having (0 <> (/* select#2 */ select (/* select#3 */ select (/* select#4 */ select count(`testdb`.`t2`.`b`))))) -
Examinez le plan d'exécution avec l'optimisation activée :
SET simplify_subq_mode = 'ON'; EXPLAIN SELECT SUM(a) FROM t2 HAVING (SELECT(SELECT(SELECT count(b)))); SHOW WARNINGS;Résultat — toute imbrication est éliminée :
/* select#1 */ select sum(`testdb`.`t2`.`a`) AS `SUM(a)` from `testdb`.`t2` having (0 <> count(`testdb`.`t2`.`b`))
Scénario 2 : Pré-évaluer les sous-requêtes [NOT] EXISTS
Cas d'application : L'optimiseur peut déterminer statiquement qu'une sous-requête EXISTS ou NOT EXISTS retourne toujours vrai (non vide) ou faux (vide).
Modèle de réécriture :
-- Non-empty result: EXISTS clause is removed entirely
-- Before
SELECT * FROM t1 WHERE EXISTS(SELECT MAX(a) FROM t2);
-- After
SELECT * FROM t1
-- Empty result (WHERE/HAVING evaluates to false, or LIMIT 0): replaced with false
-- Before
SELECT * FROM t1 WHERE EXISTS(SELECT max(a) FROM t2 HAVING 1=2);
-- After
SELECT * FROM t1 WHERE false
Cela empêche totalement l'exécution de la sous-requête.
Vérification par l'exemple :
-
Préparez les données de test :
DROP TABLE IF EXISTS t1; DROP TABLE IF EXISTS t2; CREATE TABLE t1 (id INT); CREATE TABLE t2 (val INT); INSERT INTO t1 VALUES (1), (2); -
Examinez le plan d'exécution sans optimisation :
SET simplify_subq_mode = 'OFF'; EXPLAIN SELECT * FROM t1 WHERE EXISTS(SELECT MAX(a) FROM t2); SHOW WARNINGS;Résultat — la sous-requête EXISTS est évaluée au moment de l'exécution :
/* select#1 */ select `testdb`.`t1`.`id` AS `id` from `testdb`.`t1` where exists(/* select#2 */ select max(`testdb`.`t1`.`id`) from `testdb`.`t2`) -
Examinez le plan d'exécution avec l'optimisation activée :
SET simplify_subq_mode = 'ON'; EXPLAIN SELECT * FROM t1 WHERE EXISTS(SELECT MAX(a) FROM t2); SHOW WARNINGS;Résultat — la clause EXISTS a disparu :
/* select#1 */ select `testdb`.`t1`.`id` AS `id` from `testdb`.`t1`
Scénario 3 : Ajouter LIMIT 1 aux projections constantes dans les sous-requêtes ANY/ALL
Cas d'application : La sous-requête située dans une clause ANY ou ALL sélectionne uniquement une constante (aucune référence de colonne). Récupérer plusieurs lignes identiques étant inefficace, l'optimiseur injecte LIMIT 1.
Modèle de réécriture :
-- Before
SELECT * FROM t1 WHERE a > ANY (SELECT 1 FROM t2);
-- After
SELECT * FROM t1 WHERE a > ANY (SELECT 1 FROM t2 LIMIT 1);
Cette technique évite un balayage complet de la table t2 lorsque le résultat constant est identique pour chaque ligne.
Mise en œuvre en production
Avant d'activer l'optimisation des sous-requêtes pour des charges de travail critiques, suivez ces étapes :
Exécutez d'abord des tests de régression complets dans un environnement de pré-production. Dans de rares cas limites — par exemple des requêtes dépendant d'un ordre d'exécution spécifique ou du nombre d'exécutions de sous-requêtes — l'optimisation peut entraîner un comportement différent, bien que l'équivalence logique soit garantie dans la plupart des situations.
Maintenez les statistiques des tables à jour. Le scénario 2 repose sur ces statistiques pour déduire si un ensemble est vide ou non. Exécutez régulièrement
ANALYZE TABLEafin que l'optimiseur prenne des décisions précises.