MySQL - SELECT WHERE field IN (subquery) - Ekstremalnie powolne dlaczego?

133

Mam kilka duplikatów w bazie danych, które chcę sprawdzić, więc co zrobiłem, aby zobaczyć, które są duplikatami, zrobiłem to:

SELECT relevant_field
FROM some_table
GROUP BY relevant_field
HAVING COUNT(*) > 1

W ten sposób otrzymam wszystkie wiersze z odpowiednim_polem występującym więcej niż raz. Wykonanie tego zapytania zajmuje milisekundy.

Teraz chciałem sprawdzić każdy z duplikatów, więc pomyślałem, że mogę WYBRAĆ każdy wiersz w some_table z odpowiednim_polem w powyższym zapytaniu, więc zrobiłem tak:

SELECT *
FROM some_table 
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)

Z jakiegoś powodu okazuje się to niezwykle powolne (zajmuje to kilka minut). Co dokładnie się tu dzieje, że jest tak powolny? odpowiednie_field jest indeksowane.

Ostatecznie spróbowałem utworzyć widok „temp_view” na podstawie pierwszego zapytania (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1), a następnie wykonałem drugie zapytanie w ten sposób:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM temp_view
)

I to działa dobrze. MySQL robi to w kilka milisekund.

Czy są tu jacyś eksperci SQL, którzy mogą wyjaśnić, co się dzieje?

quano
źródło
czego dokładnie chcesz? chcesz usunąć zduplikowane wpisy oprócz jednego ?? Sugestia: przeczytaj Self Join
diEcho
1
oczywiście jest to grupowanie, które jest powolne ...
ajreal
Pierwsza kwerenda jest wykonywana w milisekundach (ta grupująca i filtrująca z HAVING). Tylko w połączeniu z innym zapytaniem wszystko działa wolno (zajmuje to kilka minut).
quano
@diEcho, chcę znaleźć duplikaty, sprawdzić je i usunąć ręcznie.
quano

Odpowiedzi:

112

Przepisz zapytanie do tego

SELECT st1.*, st2.relevant_field FROM sometable st1
INNER JOIN sometable st2 ON (st1.relevant_field = st2.relevant_field)
GROUP BY st1.id  /* list a unique sometable field here*/
HAVING COUNT(*) > 1

Myślę, że st2.relevant_fieldmusi być w selekcji, bo inaczej havingklauzula da błąd, ale nie jestem w 100% pewien

Nigdy nie używaj INz podzapytaniem; jest to notorycznie powolne.
Używaj tylko INze stałą listą wartości.

Więcej podpowiedzi

  1. Jeśli chcesz, aby zapytania były szybsze, nie SELECT *wybieraj tylko tych pól, których naprawdę potrzebujesz.
  2. Upewnij się, że masz włączony indeks, relevant_fieldaby przyspieszyć sprzężenie równe.
  3. Upewnij się, że group byklucz podstawowy.
  4. Jeśli korzystasz z InnoDB i wybierasz tylko indeksowane pola (a rzeczy nie są zbyt skomplikowane), MySQL rozwiąże twoje zapytanie przy użyciu tylko indeksów, przyspieszając wszystko.

Ogólne rozwiązanie dla 90% Twoich IN (select zapytań

Użyj tego kodu

SELECT * FROM sometable a WHERE EXISTS (
  SELECT 1 FROM sometable b
  WHERE a.relevant_field = b.relevant_field
  GROUP BY b.relevant_field
  HAVING count(*) > 1) 
Johan
źródło
1
Możesz również napisać to za pomocą HAVING COUNT(*) > 1. W MySQL jest to zwykle szybsze.
ypercubeᵀᴹ
@ypercube, zrobione dla dolnego zapytania, myślę, że dla górnego zapytania zmieni wynik.
Johan
@Johan: Ponieważ st2.relevant_fieldnie jest NULL(jest już zawarty w ONklauzuli), nie zmieni to wyniku.
ypercubeᵀᴹ
@ypercube, więc możesz zmienić count (poza polem) na count (*), jeśli na pewno afieldnigdy nie będzie null, rozumiem . Dzięki
Johan,
1
@quano, tak, wyświetla listę wszystkich duplikatów, ponieważ group byjest włączona st1.id, a nie włączona st1.relevant_field.
Johan
110

Podzapytanie jest uruchamiane dla każdego wiersza, ponieważ jest to zapytanie skorelowane. Można przekształcić zapytanie skorelowane w zapytanie nieskorelowane, wybierając wszystko z podzapytania, na przykład:

SELECT * FROM
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
) AS subquery

Ostateczne zapytanie wyglądałoby następująco:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT * FROM
    (
        SELECT relevant_field
        FROM some_table
        GROUP BY relevant_field
        HAVING COUNT(*) > 1
    ) AS subquery
)
quano
źródło
3
To zadziałało dla mnie zadziwiająco dobrze. Miałem kolejne IN (podzapytanie) w ramach IN (podzapytanie) i trwało to ponad 10 minut, tak długo, że szukałem w Google podczas oczekiwania. Zawijanie każdego podzapytania w SELECT * FROM (), zgodnie z sugestią, skróciło to do 2 sekund!
Liam
DZIĘKUJĘ, już od kilku godzin próbuję wymyślić dobry sposób, aby to zrobić. To działało doskonale. Chciałbym móc dać ci więcej głosów za! To zdecydowanie powinna być odpowiedź.
thaspius
Działa świetnie. Zapytanie, które trwało ~ 50 sekund, jest teraz natychmiastowe. Chciałbym móc głosować więcej. Czasami nie możesz użyć złączeń, więc to jest właściwa odpowiedź.
simon
Zastanawiam się, dlaczego optymalizator uważa zapytania ze związkami skorelowanymi ... W każdym razie ta sztuczka zadziałała jak magia
Brian Leishman
2
Czy mógłbyś wyjaśnić, dlaczego jest to skorelowane podzapytanie? Rozumiem, że podzapytanie staje się skorelowane, gdy używa wartości zależnej od zapytania zewnętrznego. Ale w tym przykładzie nie widzę żadnych współzależności. Dałoby ten sam wynik dla każdego wiersza zwróconego przez zewnętrzne zapytanie. Mam podobny przykład wdrażany w MariaDB i nie widzę (jak dotąd) żadnego spadku wydajności, więc chciałbym wyraźnie zobaczyć, kiedy to SELECT *opakowanie jest potrzebne.
sbnc.eu
6

Podzapytania a sprzężenia

http://www.scribd.com/doc/2546837/New-Subquery-Optimizations-In-MySQL-6

edze
źródło
Podejrzewałem coś takiego, że podzapytanie jest uruchamiane dla każdego wiersza.
quano
Niektóre wersje MySQL nawet nie używają indeksu w IN. Dodałem kolejny link.
edze
1
MySQL 6 nie jest jeszcze stabilny, nie polecałbym tego do produkcji!
Johan
1
Nie polecałbym tego. Ale tutaj jest wyjaśnione, jak działa wewnętrznie (4,1 / 5.x -> 6). To pokazuje kilka pułapek obecnych wersji.
edze
5
SELECT st1.*
FROM some_table st1
inner join 
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)st2 on st2.relevant_field = st1.relevant_field;

Wypróbowałem zapytanie w jednej z moich baz danych, a także przepisałem je jako sprzężenie z zapytaniem podrzędnym.

To działało dużo szybciej, spróbuj!

ceteras
źródło
Tak, prawdopodobnie utworzy to tabelę tymczasową z wynikami grupowymi, więc będzie miała taką samą prędkość jak wersja widoku. Ale plany zapytań powinny mówić prawdę.
ypercubeᵀᴹ
3

Spróbuj tego

SELECT t1.*
FROM 
 some_table t1,
  (SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT (*) > 1) t2
WHERE
 t1.relevant_field = t2.relevant_field;
user2244323
źródło
2

Sformatowałem Twoje powolne zapytanie sql z adresem www.prettysql.net

SELECT *
FROM some_table
WHERE
 relevant_field in
 (
  SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT ( * ) > 1
 );

Używając tabeli zarówno w zapytaniu, jak i podzapytaniu, należy zawsze używać aliasów do obu, na przykład:

SELECT *
FROM some_table as t1
WHERE
 t1.relevant_field in
 (
  SELECT t2.relevant_field
  FROM some_table as t2
  GROUP BY t2.relevant_field
  HAVING COUNT ( t2.relevant_field ) > 1
 );

To pomaga?

plang
źródło
1
To niestety nie pomaga. Wykonuje się równie wolno.
quano
Zaktualizowałem odpowiedź, czy możesz spróbować ponownie? Nawet jeśli grupowanie jest powolne, powinno być wykonane tylko raz ...
plang
Ostatnim razem przypadkowo zabiłem działający serwer mysql, więc obawiam się, że nie mogę teraz tego spróbować. Później będę musiał skonfigurować testową bazę danych. Ale nie rozumiem, dlaczego powinno to wpłynąć na zapytanie. Instrukcja HAVING powinna mieć zastosowanie tylko do zapytania, w którym się znajduje, prawda? Naprawdę nie rozumiem, dlaczego „prawdziwe” zapytanie powinno wpływać na podzapytanie.
quano
Znalazłem to: xaprb.com/blog/2006/04/30/… . Myślę, że to może być rozwiązanie. Spróbuję, kiedy będę miał czas.
quano
2

Po pierwsze, możesz znaleźć zduplikowane wiersze i znaleźć liczbę wierszy używaną ile razy i uporządkować według liczby w ten sposób;

SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

następnie utwórz tabelę i wstaw do niej wynik.

create table CopyTable 
SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

Na koniec usuń wiersze do opublikowania. Numer zaczyna się od 0. Z wyjątkiem pierwszego numeru każdej grupy usuń wszystkie wiersze do opublikowania.

delete from  CopyTable where No!= 0;

harun ugur
źródło
1

czasami, gdy dane rosną, mysql WHERE IN może działać dość wolno z powodu optymalizacji zapytań. Spróbuj użyć STRAIGHT_JOIN, aby powiedzieć mysql, aby wykonywał zapytanie tak, jak jest, np

SELECT STRAIGHT_JOIN table.field FROM table WHERE table.id IN (...)

ale uwaga: w większości przypadków optymalizator mysql działa całkiem nieźle, więc polecam go używać tylko wtedy, gdy masz tego rodzaju problem

Andrey Posudevsky
źródło
0

Jest to podobne do mojego przypadku, w którym mam tabelę o nazwie tabel_buku_besar. To, czego potrzebuję, to

  1. Patrząc na zapis, że mają account_code='101.100'w tabel_buku_besarktórych companyarea='20000'a także IDRjakocurrency

  2. Muszę pobrać wszystkie rekordy, z tabel_buku_besarktórych kod_konta jest taki sam jak w kroku 1, ale transaction_numberwynik w kroku 1

podczas używania select ... from...where....transaction_number in (select transaction_number from ....)moje zapytanie działa bardzo wolno i czasami powoduje przekroczenie limitu czasu żądania lub powoduje, że moja aplikacja nie odpowiada ...

Próbuję tej kombinacji i efekt ... nieźle ...

`select DATE_FORMAT(L.TANGGAL_INPUT,'%d-%m-%y') AS TANGGAL,
      L.TRANSACTION_NUMBER AS VOUCHER,
      L.ACCOUNT_CODE,
      C.DESCRIPTION,
      L.DEBET,
      L.KREDIT 
 from (select * from tabel_buku_besar A
                where A.COMPANYAREA='$COMPANYAREA'
                      AND A.CURRENCY='$Currency'
                      AND A.ACCOUNT_CODE!='$ACCOUNT'
                      AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) L 
INNER JOIN (select * from tabel_buku_besar A
                     where A.COMPANYAREA='$COMPANYAREA'
                           AND A.CURRENCY='$Currency'
                           AND A.ACCOUNT_CODE='$ACCOUNT'
                           AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) R ON R.TRANSACTION_NUMBER=L.TRANSACTION_NUMBER AND R.COMPANYAREA=L.COMPANYAREA 
LEFT OUTER JOIN master_account C ON C.ACCOUNT_CODE=L.ACCOUNT_CODE AND C.COMPANYAREA=L.COMPANYAREA 
ORDER BY L.TANGGAL_INPUT,L.TRANSACTION_NUMBER`
Hilarius L. Doren
źródło
0

Uważam, że jest to najbardziej efektywne w wyszukiwaniu, czy wartość istnieje, logikę można łatwo odwrócić, aby znaleźć, czy wartość nie istnieje (tj. JEST NULL);

SELECT * FROM primary_table st1
LEFT JOIN comparision_table st2 ON (st1.relevant_field = st2.relevant_field)
WHERE st2.primaryKey IS NOT NULL

* Zastąp odpowiednie_pole nazwą wartości, którą chcesz sprawdzić, czy istnieje w Twojej tabeli

* Zastąp primaryKey nazwą kolumny klucza podstawowego w tabeli porównawczej.

Matt
źródło