2015-08-21 15 views
7

Potrzebuję wybrać niektóre dane z MySQL DB przy użyciu PHP. Można to zrobić w ramach jednej kwerendy MySQL, która trwa 5 minut, aby uruchomić na dobrym serwerze (wiele JOINów w tabelach z więcej niż 10 wierszami Mio).Powolne kwerendy MySQL - Cache danych w tablicy PHP?

Zastanawiam się, czy lepiej jest podzielić zapytanie w PHP i użyć trochę pętli niż MySQL. Czy byłoby lepiej, aby wysłać zapytanie do wszystkich wiadomości e-mail z jednej tabeli z 150 000 wierszy w tablicy, a następnie sprawdzić tablicę, zamiast wykonywać tysiące WYBORÓW MySQL.

Oto Zapytanie:

SELECT count(contacted_emails.id), contacted_emails.email 
FROM contacted_emails 
LEFT OUTER JOIN blacklist ON contacted_emails.email = blacklist.email 
LEFT OUTER JOIN submission_authors ON contacted_emails.email = submission_authors.email 
LEFT OUTER JOIN users ON contacted_emails.email = users.email 
GROUP BY contacted_emails.email 
HAVING count(contacted_emails.id) > 3 

wyjaśniania powraca: EXPLAIN

Indeksy w 4 stoły są:

contacted_emails: id, blacklist_section_id, journal_id and mail 
blacklist: id, email and name 
submission_authors: id, hash_key and email 
users: id, email, firstname, lastname, editor_id, title_id, country_id, workplace_id 

jobtype_id

The contacted_emails stołowych jest tworzony na przykład:

CREATE TABLE contacted_emails ( 
    id int(10) unsigned NOT NULL AUTO_INCREMENT, 
    email varchar(150) COLLATE utf8_unicode_ci NOT NULL, 
    contacted_at datetime NOT NULL, 
    created_at datetime NOT NULL, 
    blacklist_section_id int(11) unsigned NOT NULL, 
    journal_id int(10) DEFAULT NULL, 
    PRIMARY KEY (id), 
    KEY blacklist_section_id (blacklist_section_id), 
    KEY journal_id (journal_id), 
    KEY email (email)) 
ENGINE=InnoDB AUTO_INCREMENT=4491706 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 
+4

Zgodnie z ogólną zasadą SQL będzie ZAWSZE szybszy niż PHP. Jeśli twoje zapytanie trwa 5 minut, nawet z milionami rekordów i wieloma połączeniami, założę się, że jest gdzieś nieoptymalna składnia lub brakujący indeks. Powinieneś wykonać polecenie EXPLAIN, aby sprawdzić plan wykonania zapytania w celu dalszej optymalizacji. –

+1

Powinieneś odesłać bardziej szczegółowe pytanie pokazujące twoje zapytanie i WYJAŚNIJ wyjście i zobacz czy ktoś może to naprawić. –

+0

@StevenMoseley, dzięki. Proszę zobaczyć zapytanie w edytowanym pytaniu. Duża tabela to contacted_emails, która ma 10 Mio wierszy. Muszę wiedzieć, który e-mail znajduje się w contacted_mails, a nie w użytkownikach, a nie w submode_authors i skontaktowano się z nim ponad 3 razy. –

Odpowiedz

0

po swój Zalecenia, wybieram to rozwiązanie:

SELECT ce.email, ce.number_of_contacts 
FROM (
    SELECT email, COUNT(id) AS number_of_contacts 
    FROM contacted_emails 
    GROUP BY email 
    HAVING number_of_contacts > 3 
) AS ce 
NATURAL LEFT JOIN blacklist AS bl 
NATURAL LEFT JOIN submission_authors AS sa 
NATURAL LEFT JOIN users AS u 
WHERE bl.email IS NULL AND sa.email IS NULL AND u.email IS NULL 

Trwa to 10 sekund, aby przejść, co jest dobre w tej chwili. Gdy będę miał więcej danych w bazie danych, będę musiał pomyśleć o innym rozwiązaniu, w którym utworzę tabelę tymczasową.

Podsumowując, załadowanie całej tabeli jako tablicy php nie jest dobre dla wydajności, jak tworzenie zapytań mysql.

+1

Czy próbowałeś zmienić 'COUNT (id)' na 'COUNT (*)'? Chciałbym wiedzieć, czy zwiększyło to wydajność. Podobnie jak już zrobiłeś licznik, możesz użyć 'HAVING number_of_contacts> 3' w podzapytaniu. – Arth

+0

@Arth, zmiana 'COUNT (id)' na 'COUNT (*)' nie ma wpływu na wydajność. Jednak zmiana 'HAVING COUNT (id)> 3' na' HAVING number_of_contacts> 3' poprawiła wydajność (z 20sec do 10sec). Redagowałem odpowiedź, wielkie dzięki. –

2

Kilka myśli, jeśli chodzi o zapytania można go znaleźć szybciej, jeśli

count(*) row_count 

i zmienić HAVING do

row_count > 3 

jak to może być zadowolony z contacted_emails.email indeks bez konieczności dostępu do wiersza, aby uzyskać contacted_emails.id. Ponieważ oba pola są NOT NULL i contacted_emails jest podstawową tabelą, powinna to być ta sama logika.

Ponieważ zapytanie to będzie się wydłużyć, gdy zbierzesz więcej danych, proponuję tabelę podsumowującą, w której przechowujesz liczniki (prawdopodobnie za jednostkę czasu). Może to być okresowo aktualizowane za pomocą cronjob lub w locie z wyzwalaczami i/lub logiką aplikacji.

Jeśli używasz opcji jednostki na jednostkę czasu na created_at i/lub przechowujesz ostatnią aktualizację do crona, powinieneś być w stanie uzyskać wyniki na żywo, pobierając i dołączając najnowsze dane.

Każde rozwiązanie z pamięcią podręczną musi zostać zmodyfikowane, aby pozostać na żywo, a pełne zapytanie powinno być uruchamiane za każdym razem, gdy dane zostaną wyczyszczone/zaktualizowane.

Jak zasugerowano w komentarzach, baza danych jest zbudowana do agregowania dużych ilości danych. PHP nie jest.

+0

Jeśli liczyć e-mail z HAVING, musisz użyć DISTINCT, który jest dość wolny. – Mihai

+0

@Mihai Yep, nie jestem pewien, czy całkowicie zgadzasz się z DISTINCT, ale źle odczytałem grupę, wyciągnę tę sugestię. – Arth

2

Prawdopodobnie byłbyś najlepszy z tabelą Podsumowanie, która jest aktualizowana za pomocą wyzwalacza przy każdej wstawce do tabeli kontaktów e-mail. Ta tabela Podsumowanie powinna zawierać adres e-mail i kolumnę zliczania. Każda wstawka do tabeli ze stykiem, aktualizuj licznik. Mieć indeks na kolumnie licznika w tabeli podsumowań. Następnie możesz zapytać bezpośrednio od TEGO, mieć konto e-mail, o którym mowa, THEN dołączyć, aby uzyskać wszystkie pozostałe dane, które należy pociągnąć.

+0

To nie jest rozsądne rozwiązanie. Jeśli za każdym razem, gdy będziemy musieli agregować dane, powinniśmy tworzyć tabele "zliczania", nasze zadania, które będą ssać programiści. Liczba zostanie zsynchronizowana. Marketing zdecydowałby, że chcą średnich, lub liczy na miesiąc, lub cokolwiek innego. Potem znowu musimy przerobić całe zhakowane programowanie. Właśnie dlatego istnieje SQL - aby wykonywać te złożone zadania w locie, więc NIE POTRZEBUJEMY list danych zagregowanych. –

+1

@StevenMoseley, z całym szacunkiem się nie zgadzam. W niektórych przypadkach jest to zależne od kontekstu danych witryn, a nawet ogólnie od eksploracji danych. Jeśli uruchomione zostaną wyzwalacze, aby zaktualizować jakiekolwiek agregaty, roll-upy itp., Odpytywanie z tego jako podstawy MOŻE być szybsze. Tabela jest tworzona RAZEM, a wyzwalacze w tabeli OTHER wykonają wstawienie/aktualizację. Po ustaleniu podstawowych kryteriów, wiercenie w szczegóły dostanie się do bardziej surowych danych. – DRapp

3

Twoje indeksy wyglądają dobrze.

Problemy z wydajnością wydają się wynikać z faktu, że jesteś JOIN wszystkie wiersze, a następnie filtrowanie przy użyciu HAVING.

To prawdopodobnie zamiast pracować lepiej:

SELECT * 
FROM (
    SELECT email, COUNT(id) AS number_of_contacts 
    FROM contacted_emails 
    GROUP BY email 
    HAVING COUNT(id) > 3 
) AS ce 
LEFT OUTER JOIN blacklist AS bl ON ce.email = bl.email 
LEFT OUTER JOIN submission_authors AS sa ON ce.email = sa.email 
LEFT OUTER JOIN users AS u ON ce.email = u.email 
/* EDIT: Exclude-join clause added based on comments below */ 
WHERE bl.email IS NULL 
    AND sa.email IS NULL 
    AND u.email IS NULL 

Tutaj jesteś ograniczając swoje początkowe GROUP dane ed ustawione przed JOIN S, który jest znacznie bardziej optymalne.

Chociaż biorąc pod uwagę kontekst pierwotnym zapytaniu, że LEFT OUTER JOIN stoły dom't wydają się być w ogóle stosowane, więc poniżej najprawdopodobniej powróci dokładnie te same wyniki z jeszcze mniej napowietrznych:

SELECT email, COUNT(id) AS number_of_contacts 
FROM contacted_emails 
GROUP BY email 
HAVING count(id) > 3 

Co dokładnie to jest punkt tych tabel JOIN? LEFT JOIN zapobiega ich zmniejszaniu danych, a użytkownik patrzy tylko na zbiorcze dane z contacted_emails. Czy zamiast tego używałeś INNER JOIN?


EDYCJA: Wspomniałeś, że punktem łączenia jest wykluczanie wiadomości e-mail w istniejących tabelach. Zmodyfiowałem swoje pierwsze zapytanie, aby wykonać odpowiednie wykluczenie (to był błąd w oryginalnie opublikowanym kodzie).

Oto kolejna możliwa opcja, że ​​może dobrze wykonać dla Państwa:

SELECT 
FROM contacted_emails 
LEFT JOIN (
    SELECT email FROM blacklist 
    UNION ALL SELECT email FROM submission_authors 
    UNION ALL SELECT email FROM users 
) AS existing ON contacted_emails.email = existing.email 
WHERE existing.email IS NULL 
GROUP BY contacted_emails.email 
HAVING COUNT(id) > 3 

Co ja robię tu jest gromadzenie istniejących wiadomości w podkwerendzie i robi jedno wyklucza przyłączenia na tej tabeli pochodzą.

Innym sposobem można spróbować wyrazić to jako nie skorelowane podzapytania w klauzuli WHERE:

SELECT 
FROM contacted_emails 
WHERE email NOT IN (
    SELECT email FROM blacklist 
    UNION ALL SELECT email FROM submission_authors 
    UNION ALL SELECT email FROM users 
) 
GROUP BY email 
HAVING COUNT(id) > 3 

Wypróbuj je wszystkie i zobaczyć, co daje najlepszy plan wykonania w MySQL

+0

Witaj Steven, dziękuję ci za odpowiedź. 'LEFT OUTER JOIN' służy do wykluczania wiadomości e-mail, które są już w tabelach' USERS', 'submission_authors' i' blacklist'. Potrzebuję tych e-maili do wykluczenia. –

+0

@ Miloš - W takim przypadku do wykluczenia należy użyć filtru IS NULL. Edytuję moją odpowiedź. –