Sammanfattning:Detta är ett känt problem i MySQL och fixades i MySQL 5.6.x. Problemet beror på en saknad optimering när en underfråga som använder IN felaktigt identifieras som en beroende underfråga istället för en oberoende underfråga.
När du kör EXPLAIN på den ursprungliga frågan returnerar den detta:
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'
När du ändrar IN
till =
du får detta:
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'
Varje beroende underfråga körs en gång per rad i frågan den ingår i, medan underfrågan endast körs en gång. MySQL kan ibland optimera beroende delfrågor när det finns ett villkor som kan konverteras till en join men det är inte fallet här.
Nu lämnar detta naturligtvis frågan om varför MySQL anser att IN-versionen måste vara en beroende underfråga. Jag har gjort en förenklad version av frågan för att undersöka detta. Jag skapade två tabeller 'foo' och 'bar' där den förra endast innehåller en id-kolumn, och den senare innehåller både ett id och ett foo-id (även om jag inte skapade en främmande nyckel-begränsning). Sedan fyllde jag i båda tabellerna med 1000 rader:
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
);
Denna förenklade fråga har samma problem som tidigare - det inre urvalet behandlas som en beroende underfråga och ingen optimering utförs, vilket gör att den inre frågan körs en gång per rad. Frågan tar nästan en sekund att köra. Ändra IN
till =
tillåter återigen att frågan körs nästan omedelbart.
Koden jag använde för att fylla i tabellerna finns nedan, ifall någon skulle vilja återskapa resultaten.
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;