2010-08-05 13 views

Odpowiedz

45

Podsumowanie: Jest to known problem w MySQL i została ustalona w MySQL 5.6.x. Problem wynika z braku optymalizacji, gdy podzapytanie z IN jest nieprawidłowo rozpoznane jako zależne podzapytanie zamiast niezależnego podzapytania.


Po uruchomieniu Wyjaśnij na pierwotnym zapytaniu powraca to:

 
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 = masz 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żdy Podzapytanie zależny jest prowadzony 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 sprzężenie, ale tutaj tak nie jest.

To oczywiście pozostawia pytanie, dlaczego MySQL uważa, że ​​wersja IN musi być zależnym podzapytaniem. Zrobiłem uproszczoną wersję zapytania, aby pomóc to zbadać. Stworzyłem dwie tabele "foo" i "bar", gdzie pierwsza zawiera jedynie kolumnę id, a druga zawiera id i foo id (chociaż nie utworzyłem ograniczenia klucza obcego). Następnie wypełnione oba stoły 1000 rzędach

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 
); 

Ten uproszczony zapytania ma ten sam problem jak poprzednio - wewnętrzna Select jest traktowany jako zależną podzapytaniu i nie optymalizacja prowadzi się, przez co wewnętrzne zapytanie być prowadzone raz rząd. Wykonanie zapytania trwa prawie jedną sekundę. Ponowna zmiana IN na = pozwala na niemal natychmiastowe uruchomienie zapytania.

Kod użyty 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; 
+2

Czy istnieje sposób zmuszenia optymalizatora do traktowania podkwerendy jako tylko podzapytanie, a nie podzadania zależnego? –

+0

@Itay Moav: MySQL powinien sam być w stanie wyliczyć, które podzapytania są zależne od zewnętrznych zapytań. Nadal jestem trochę zaskoczony, że w tym przypadku uważa, że ​​zapytanie wewnętrzne jest zapytaniem zależnym, gdy wyraźnie nie ma odniesienia do oryginalnej tabeli. Mogę przeszukać bazę danych błędów, aby sprawdzić, czy ktoś zgłosił ten problem. –

+0

@Itay Moav: Uprościliśmy zapytanie i zreplikowałem ten sam problem w prostszym zapytaniu. Znalazłem raport o błędzie w MySQL, który opisuje dokładnie ten sam problem. Programiści MySQL obiecują poprawkę. Odpowiednio zaktualizowałem swoją odpowiedź. Mam nadzieję, że to w pełni odpowie na twoje pytanie. PS: +1 za dobre pytanie, które wymagało ode mnie pewnych badań! :) –

0

Optymalizatory SQL nie zawsze robią to, czego oczekują od użytkownika. Nie jestem pewien, czy jest lepsza odpowiedź. Dlatego musisz sprawdzić wyniki EXPLAIN PLAN i profilować swoje zapytania, aby dowiedzieć się, gdzie znajduje się czas.

+0

+1 dla polecenia EXPLAIN jako punkt wyjścia do analizy wydajności zapytań . – Cumbayah

1

Chodzi o wewnętrzne kwerendy a.k.a podkwerendy kontra sprzężenia, a nie o IN vs =, a przyczyny są wyjaśnione w tym poście. Wersja 5.4 MySQL ma na celu wprowadzenie ulepszonego optymalizatora, który może przepisać niektóre podzapytania na bardziej wydajną formę.

Najgorszą rzeczą, jaką możesz zrobić, jest użycie tzw skorelowane podzapytanie http://dev.mysql.com/doc/refman/5.1/en/correlated-subqueries.html

0

To ciekawe, ale problem może być rozwiązany z sporządzanych sprawozdań (nie wiem, czy jest ona odpowiednia dla wszystkich), np:

mysql> EXPLAIN SELECT * FROM words WHERE word IN (SELECT word FROM phrase_words); 
+----+--------------------+--------------+... 
| id | select_type  | table  |... 
+----+--------------------+--------------+... 
| 1 | PRIMARY   | words  |... 
| 2 | DEPENDENT SUBQUERY | phrase_words |... 
+----+--------------------+--------------+... 
mysql> EXPLAIN SELECT * FROM words WHERE word IN ('twist','rollers'); 
+----+-------------+-------+... 
| id | select_type | table |... 
+----+-------------+-------+... 
| 1 | SIMPLE  | words |... 
+----+-------------+-------+... 

Więc po prostu przygotować oświadczenie w procedurze przechowywanej , a następnie wykonaj to.Oto pomysł:

SET @words = (SELECT GROUP_CONCAT(word SEPARATOR '\',\'') FROM phrase_words); 
SET @words = CONCAT("'", @words, "'"); 
SET @query = CONCAT("SELECT * FROM words WHERE word IN (", @words, ");"; 
PREPARE q FROM @query; 
EXECUTE q; 
+0

jeśli chcesz iść tą trasą, utwórz w SP tymczasową tabelę z wartościami, które chcesz w IN i dołącz do głównej tabeli. –

+0

To jest dobra uwaga, wielkie dzięki! – Maksim

Powiązane problemy