Résumé :Il s'agit d'un problème connu dans MySQL et a été corrigé dans MySQL 5.6.x. Le problème est dû à une optimisation manquante lorsqu'une sous-requête utilisant IN est incorrectement identifiée comme sous-requête dépendante au lieu d'une sous-requête indépendante.
Lorsque vous exécutez EXPLAIN sur la requête d'origine, cela renvoie ceci :
1 'PRIMARY' 'question_law_version' 'ALL' '' '' '' '' 10148 'Using where' 2 'DEPENDENT SUBQUERY' 'question_law_version' 'ALL' '' '' '' '' 10148 'Using where' 3 'DEPENDENT SUBQUERY' 'question_law' 'ALL' '' '' '' '' 10040 'Using where'
Lorsque vous modifiez IN
à =
vous obtenez ceci :
1 'PRIMARY' 'question_law_version' 'ALL' '' '' '' '' 10148 'Using where' 2 'SUBQUERY' 'question_law_version' 'ALL' '' '' '' '' 10148 'Using where' 3 'SUBQUERY' 'question_law' 'ALL' '' '' '' '' 10040 'Using where'
Chaque sous-requête dépendante est exécutée une fois par ligne dans la requête dans laquelle elle est contenue, tandis que la sous-requête n'est exécutée qu'une seule fois. MySQL peut parfois optimiser les sous-requêtes dépendantes lorsqu'il existe une condition pouvant être convertie en jointure, mais ici ce n'est pas le cas.
Maintenant, cela laisse bien sûr la question de savoir pourquoi MySQL pense que la version IN doit être une sous-requête dépendante. J'ai fait une version simplifiée de la requête pour aider à enquêter sur cela. J'ai créé deux tables 'foo' et 'bar' où la première ne contient qu'une colonne id, et la seconde contient à la fois un id et un foo id (bien que je n'ai pas créé de contrainte de clé étrangère). Ensuite, j'ai rempli les deux tables avec 1 000 lignes :
CREATE TABLE foo (id INT PRIMARY KEY NOT NULL);
CREATE TABLE bar (id INT PRIMARY KEY, foo_id INT NOT NULL);
-- populate tables with 1000 rows in each
SELECT id
FROM foo
WHERE id IN
(
SELECT MAX(foo_id)
FROM bar
);
Cette requête simplifiée présente le même problème qu'auparavant :la sélection interne est traitée comme une sous-requête dépendante et aucune optimisation n'est effectuée, ce qui entraîne l'exécution de la requête interne une fois par ligne. La requête prend presque une seconde pour s'exécuter. Modification du IN
à =
permet à nouveau à la requête de s'exécuter presque instantanément.
Le code que j'ai utilisé pour remplir les tableaux est ci-dessous, au cas où quelqu'un souhaiterait reproduire les résultats.
CREATE TABLE filler (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT
) ENGINE=Memory;
DELIMITER $$
CREATE PROCEDURE prc_filler(cnt INT)
BEGIN
DECLARE _cnt INT;
SET _cnt = 1;
WHILE _cnt <= cnt DO
INSERT
INTO filler
SELECT _cnt;
SET _cnt = _cnt + 1;
END WHILE;
END
$$
DELIMITER ;
CALL prc_filler(1000);
INSERT foo SELECT id FROM filler;
INSERT bar SELECT id, id FROM filler;