Podsumowanie:jest to znany problem w MySQL i został naprawiony w MySQL 5.6.x. Problem jest spowodowany brakiem optymalizacji, gdy podzapytanie używające IN jest nieprawidłowo identyfikowane jako zależne podzapytanie zamiast niezależnego podzapytania.
Po uruchomieniu polecenia EXPLAIN w pierwotnym zapytaniu zwraca ono następujące polecenie:
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'
Po zmianie IN
do =
otrzymujesz to:
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'
Każde zależne podzapytanie jest uruchamiane raz na wiersz w zapytaniu, w którym się znajduje, podczas gdy podzapytanie jest uruchamiane tylko raz. MySQL może czasami optymalizować zależne podzapytania, gdy istnieje warunek, który można przekonwertować na łączenie, ale w tym przypadku tak nie jest.
To oczywiście pozostawia pytanie, dlaczego MySQL uważa, że wersja IN musi być podzapytaniem zależnym. Zrobiłem uproszczoną wersję zapytania, aby pomóc to zbadać. Utworzyłem dwie tabele „foo” i „bar”, gdzie pierwsza zawiera tylko kolumnę id, a druga zawiera zarówno identyfikator, jak i identyfikator foo (chociaż nie stworzyłem ograniczenia klucza obcego). Następnie wypełniłem obie tabele 1000 wierszami:
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
);
To uproszczone zapytanie ma ten sam problem, co poprzednio — wewnętrzna selekcja jest traktowana jako zależne podzapytanie i nie jest przeprowadzana żadna optymalizacja, co powoduje, że wewnętrzne zapytanie jest uruchamiane raz na wiersz. Wykonanie zapytania zajmuje prawie sekundę. Zmiana IN
do =
ponownie umożliwia niemal natychmiastowe uruchomienie zapytania.
Kod, którego użyłem do wypełnienia tabel, znajduje się poniżej, na wypadek gdyby ktoś chciał odtworzyć wyniki.
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;