2014-09-10 14 views
12

Jestem ciekawy, jak wykonanie EXISTS() ma być szybsze niż IN().Mysql Exists vs IN - skorelowane podzapytanie vs podzapytanie?

Byłem answering a question kiedy Bill Karwin przedstawił dobry punkt. podczas korzystania z EXISTS() używa skorelowanego podkwerendy (podzapytanie zależne), a IN() używa tylko podkwerendy.

EXPLAIN pokazuje, że EXISTS i NOT EXISTS stosowania zarówno zależną podzapytania i IN/NOT IN zarówno używać tylko podkwerenda .. więc jestem ciekaw jak skorelowane podzapytanie jest szybszy niż podkwerendzie ??

Używałem EXISTS wcześniej i wykonuje się szybciej niż IN, dlatego jestem zdezorientowany.

Oto SQLFIDDLE z wyjaśnia

EXPLAIN SELECT COUNT(t1.table1_id) 
FROM table1 t1 
WHERE EXISTS 
( SELECT 1 
    FROM table2 t2 
    WHERE t2.table1_id <=> t1.table1_id 
); 

+-------+-----------------------+-----------+-------+---------------+-----------+--------+--------------------------+--------+------------------------------+ 
| ID | SELECT_TYPE   | TABLE | TYPE | POSSIBLE_KEYS | KEY  |KEY_LEN | REF      | ROWS | EXTRA      | 
+-------+-----------------------+-----------+-------+---------------+-----------+--------+--------------------------+--------+------------------------------+ 
| 1 | PRIMARY    | t1  | index | (null)  | PRIMARY | 4 | (null)     | 4 | Using where; Using index | 
| 2 | DEPENDENT SUBQUERY | t2  | REF | table1_id  | table1_id| 4 | db_9_15987.t1.table1_id | 1 | Using where; Using index | 
+-------+-----------------------+-----------+-------+---------------+-----------+--------+--------------------------+--------+------------------------------+ 

EXPLAIN SELECT COUNT(t1.table1_id) 
FROM table1 t1 
WHERE NOT EXISTS 
( SELECT 1 
    FROM table2 t2 
    WHERE t2.table1_id = t1.table1_id 
); 
+-------+-----------------------+-----------+-------+---------------+-----------+--------+--------------------------+--------+------------------------------+ 
| ID | SELECT_TYPE   | TABLE | TYPE | POSSIBLE_KEYS | KEY  |KEY_LEN | REF      | ROWS | EXTRA      | 
+-------+-----------------------+-----------+-------+---------------+-----------+--------+--------------------------+--------+------------------------------+ 
| 1 | PRIMARY    | t1  | index | (null)  | PRIMARY | 4 | (null)     | 4 | Using where; Using index | 
| 2 | DEPENDENT SUBQUERY | t2  | ref | table1_id  | table1_id| 4 | db_9_15987.t1.table1_id | 1 | Using index     | 
+-------+-----------------------+-----------+-------+---------------+-----------+--------+--------------------------+--------+------------------------------+ 

EXPLAIN SELECT COUNT(t1.table1_id) 
FROM table1 t1 
WHERE t1.table1_id NOT IN 
( SELECT t2.table1_id 
    FROM table2 t2 
); 
+-------+-------------------+-----------+-------+---------------+-----------+--------+----------+--------+------------------------------+ 
| ID | SELECT_TYPE  | TABLE | TYPE | POSSIBLE_KEYS | KEY  |KEY_LEN | REF  | ROWS | EXTRA      | 
+-------+-------------------+-----------+-------+---------------+-----------+--------+----------+--------+------------------------------+ 
| 1 | PRIMARY   | t1  | index | (null)  | PRIMARY | 4 | (null) | 4 | Using where; Using index | 
| 2 | SUBQUERY  | t2  | index | (null)  | table1_id| 4 | (null) | 2 | Using index     | 
+-------+-------------------+-----------+-------+---------------+-----------+--------+----------+--------+------------------------------+ 

kilka pytań

W wyjaśniono powyżej, w jaki sposób mają using where EXISTS i using index w dodatkach, ale NOT EXISTS nie ma using where w dodatkach?

W jaki sposób skorelowane podzapytanie jest szybsze niż podzapytanie?

+0

Czy masz repro z "exist", które działa szybciej? Również w jakiej wersji tego doświadczyłeś? 'in' również [kiedyś miał ten sam problem] (http://stackoverflow.com/q/3416076/73226) –

+0

@MartinSmith dobrze Zmieniłem moje zapytania z IN na EXISTS około rok temu, ponieważ zostały wykonane szybciej dzięki EXISTS (nie przez cały czas, coś jak pół sekundy do sekundy szybciej) .. ale właśnie dostałem nowy komputer i pobrałem najnowszą wersję MySQL .. Właśnie uruchomiłem zapytanie i IN działało szybciej o .004 sekundy ... czy była ostatnio poprawka dla planu wykonawczego/optymalizatora? –

+0

Nie wiem zbyt wiele na temat optymalizatora MySql, ale uważam, że 5.6 wprowadził pewne zmiany. https://dev.mysql.com/doc/refman/5.6/en/subquery-optymizacja.html –

Odpowiedz

8

To RDBMS-agnostyk odpowiedź, ale mimo to może pomóc. W moim rozumieniu skorelowane (zależne, zależne) podzapytanie jest prawdopodobnie najczęściej fałszywie oskarżonym winnym złego działania.

Problem (jak jest to najczęściej opisywane) polega na tym, że przetwarza zapytanie wewnętrzne dla każdego wiersza zewnętrznego zapytania. Dlatego jeśli zapytanie zewnętrzne zwróci 1000 wierszy, a zapytanie wewnętrzne zwróci wartość 10.000, zapytanie będzie musiało przejść przez 10 000 000 wierszy (zewnętrzna × wewnętrzna), aby uzyskać wynik. W porównaniu do 11000 wierszy (zewnętrzny + wewnętrzny) z nieskorelowanego zapytania w tych samych zestawach wyników, to nie jest dobre.

Jednak jest to najgorszy scenariusz. W wielu przypadkach DBMS będzie w stanie wykorzystać indeksy do drastycznego zmniejszenia liczby wierszy. Nawet jeśli wewnętrzna kwerenda może korzystać z indeksu, 10 000 wierszy ma ~ 13 poszukiwań, co zmniejsza łączną liczbę do 13 000.

Operator exists może przerwać przetwarzanie wierszy po pierwszym, zmniejszając dodatkowo koszt zapytania, szczególnie gdy większość zewnętrznych wierszy odpowiada co najmniej jednemu rzędowi wewnętrznemu.

W niektórych rzadkich przypadkach widziałem, jak SQL Server 2008R2 optymalizuje skorelowane podzapytania do łączenia (które wykonuje oba zestawy tylko raz - najlepszy możliwy scenariusz), gdzie odpowiedni indeks można znaleźć zarówno w zapytaniach wewnętrznych, jak i zewnętrznych.

Prawdziwym sprawcą złych wyników nie jest koniecznie skorelowane podzapytania, ale zagnieżdżone skany.

3

To zależy od wersji MySQL - w optymalizatorze zapytań MySQL występuje błąd w wersjach do 6.0.

Podzapytania z "IN" nie zostały poprawnie zoptymalizowane (ale wykonywane ponownie, jak te zależne). Ten błąd nie wpływa na zapytania ani połączenia.

Problem polega na tym, że w przypadku stwierdzenia, że ​​wykorzystuje się podzapytania IN, optymalizator przepisuje go jako podzapytanie skorelowane. Rozważmy następującą instrukcję, która używa nieskorelowanej podkwerendy:

SELECT ... FROM t1 WHERE t1.a IN (SELECT b FROM t2);

optymalizator przepisuje oświadczenie podzapytanie skorelowane:

SELECT ... FROM t1 WHERE EXISTS (SELECT 1 od t2 WHERE t2.b = t1.a);

jeżeli wewnętrzne i zewnętrzne zapytania powrotu M i N wierszy odpowiednio czas wykonanie będzie rzędu O (M x N), zamiast O (M + N), jak byłoby AN nieskorelowane podzapytanie.

Odsyłacze.

+2

Przypadek 'O (MxN) nie wygląda tak, jakby powinien się odnosić do PO w zasadzie, ponieważ skorelowane pod-zapytanie może używać indeksu. W MySQL, kiedy ocenia wewnętrzne, niespojonowane, pod-zapytanie, prawdopodobnie nadal musiałoby je gdzieś przechowywać i ostatecznie odtwarzać zmaterializowany wynik w taki sam sposób po wewnętrznej stronie sprzężenia zagnieżdżonych pętli? W takim przypadku zmaterializowany wynik jest indeksowany? A może użyje połączenia hash lub czegoś tutaj? –

1

Jak już wiadomo, podzapytanie nie korzysta z wartości z zapytania zewnętrznego, dlatego jest wykonywane tylko raz. Koordynowane podzapytanie jest zsynchronizowane, dlatego jest wykonywane dla każdego wiersza przetwarzanego w zapytaniu zewnętrznym.

Zaletą korzystania z EXISTS jest to, że jeśli zostanie uznane za spełnione, wykonywanie podzapytania zatrzymuje się po zwrocie co najmniej jednego wiersza. Tak więc może być szybszy niż proste podzapytanie. Ale to nie jest ogólna zasada! Wszystko zależy od wykonywanego zapytania, optymalizatora zapytań i wersji silnika wykonawczego SQL. Polecenie

EXISTS zaleca się stosować, gdy występuje np. Instrukcja warunkowa if, ponieważ jest ona z pewnością szybsza niż count.

Nie można tak naprawdę porównać obu podkwerend przy użyciu prostego testu porównawczego 4 lub 3 zapytań.

Mam nadzieję, że przyda się!