Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

 
Reply to this topicStart new topic
> Optymalizacja zapytania
Petre
post
Post #1





Grupa: Zarejestrowani
Postów: 29
Pomógł: 0
Dołączył: 12.05.2012

Ostrzeżenie: (0%)
-----


Witajcie.
Mam to "szczęście", że muszę po poprzednikach poprawić zapytania w sklepie internetowym. W znacznym stopniu baza danych zostaje obciążona czego efektem jest wydłużony czas ładowania się strony.

Zapytania wyświetlające listę produktów w sklepie:

  1. SELECT z.zdj, z.id_zest, z.symbol, zj.nazwa, zj.*,MAX(p.data_add) AS data_produkt, zc.wartosc,zc.id_cech
  2. FROM (zestawy z, zestawy_jezyki zj, produkty p, kategorie_zestawy kz, kategorie k ) LEFT JOIN produkty_jezyki pj ON p.id_prod=pj.id_prod
  3. AND pj.id_lang=1 LEFT JOIN zestawy_cechy zc ON zc.id_zest=z.id_zest AND zc.id_cech=2
  4. WHERE p.id_zest=z.id_zest AND p.stan=1 AND z.id_zest=zj.id_zest AND z.stan=1 AND zj.id_lang=1 AND kz.id_kat=k.id_kat AND z.id_zest=kz.id_zest
  5. AND p.detal=1 AND (kz.id_kat=3009 OR k.id_gr=3009)
  6. GROUP BY z.id_zest
  7. ORDER BY data_produkt DESC , zj.nazwa ASC LIMIT 0, 20



  1. SELECT count(DISTINCT z.id_zest) AS il
  2. FROM (zestawy z, zestawy_jezyki zj, produkty p, kategorie_zestawy kz, kategorie k ) LEFT JOIN produkty_jezyki pj ON p.id_prod=pj.id_prod
  3. AND pj.id_lang=1 LEFT JOIN zestawy_cechy zc ON zc.id_zest=z.id_zest AND zc.id_cech=2
  4. WHERE p.id_zest=z.id_zest AND p.stan=1 AND z.id_zest=zj.id_zest AND z.stan=1 AND zj.id_lang=1 AND kz.id_kat=k.id_kat
  5. AND z.id_zest=kz.id_zest AND p.detal=1 AND (kz.id_kat=3009 OR k.id_gr=3009)


zapytanie zostaje wywołane dwa razy:
1 dla listy produktów, 2 (jak wywnioskowałem) dla paginacji (ilość stron produktów).

Indexy założone na tabele (nazwa tabeli - pola indexow):
zestawy - id_zest(pk), id_kat, zdj, symbol;
zestawy_jezyki - id_zest_l(pk), id_zest, id_lang, nazwa, opis, ivona(nie uzywane juz w sklepie pole w bazie), ivona_url(jak w przypadku ivony)
zestawy_cechy - id_cech_l(pk) & id_zest, id_cech_l(pk), id_zest, wartosc
produkty - id_prod(pk), id_user, id_vat, id_produc, id_zest, id_rodz, data_add, idx_stan, detal
produkty_jezyki - id_prod_l(pk), id_prod, id_lang
kategorie_zestawy - id_kat_zest(pk), id_kat & id_zest
kategorie - id_kat(pk), id_user, id_gr

Wszystkie tabele to MyISAM.

Jakie są wasze sugestie co do optymalizacji tego zapytania ?

Ten post edytował Petre 3.01.2013, 10:16:50
Go to the top of the page
+Quote Post
Sephirus
post
Post #2





Grupa: Zarejestrowani
Postów: 1 527
Pomógł: 438
Dołączył: 28.06.2011
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


Witam. To jest masakra - ile lat ma ten kod? Przede wszystkim przerobił bym to na InnoDB bo jest dużo odczytów a mało zapisów (o ile nie wpłynie to na inne rzeczy w systemie - trzeba by sprawdzić ale wątpie by wpłynęło). Jak już będzie na tym mechanizmie to przerobił bym zapytanka tak by widoczne były wszystkie JOINY - czyli zamiast FROM i listy tabel dałbym to w joinach i odpowiednio owarunkował. Jak już by to działało to zerknąłbym na indeksy i ustawił (jeśli nie ma) dla wszystkich pól jakie są wykorzystywane w WHERE, ON i ORDER.

Jak będziesz miał takie zapytanka (czyli dojdziesz do tego jak to powinno wyglądać) to wtedy można je przeanalizować i ewentualnie jeszcze zoptymalizować. Sama przeróbka powinna podnieść nieco wydajność (IMG:style_emoticons/default/wink.gif)
Go to the top of the page
+Quote Post
Petre
post
Post #3





Grupa: Zarejestrowani
Postów: 29
Pomógł: 0
Dołączył: 12.05.2012

Ostrzeżenie: (0%)
-----


Nie aż tak wiele ; )

Ok przygotowałem takie coś :

  1. SELECT z.zdj, z.id_zest, z.symbol, zj.nazwa, zj.*,MAX(p.data_add) AS data_produkt, zc.wartosc,zc.id_cech
  2. FROM zestawy z
  3. JOIN zestawy_jezyki zj ON z.id_zest=zj.id_zest
  4. JOIN produkty p ON p.id_zest=z.id_zest
  5. JOIN kategorie_zestawy kz ON kz.id_zest=z.id_zest
  6. JOIN kategorie k ON kz.id_kat=k.id_kat
  7. LEFT JOIN produkty_jezyki pj ON p.id_prod=pj.id_prod
  8. LEFT JOIN zestawy_cechy zc ON zc.id_zest=z.id_zest
  9. WHERE p.stan=1 AND z.stan=1 AND zj.id_lang=1 AND p.detal=1 AND (kz.id_kat=3009 OR k.id_gr=3009) AND zc.id_cech=2 AND pj.id_lang=1
  10. GROUP BY z.id_zest
  11. ORDER BY data_produkt DESC , zj.nazwa ASC


co ciekawe, kiedy umieszczam AND zc.id_cech=2 AND pj.id_lang=1 w WHERE czas zapytania jest dłuższy niż, gdy owe pola wstawię w JOINACH

  1. LEFT JOIN produkty_jezyki pj ON p.id_prod=pj.id_prod AND pj.id_lang=1
  2. LEFT JOIN zestawy_cechy zc ON zc.id_zest=z.id_zest AND zc.id_cech=2


Czy zmiana silnika na InnoDB na żywo (w trakcie działania sklepu, a więc tym samym robionych selektach przez użytkowników itp.) nie odbije się czkawką ?
AutoInc zostanie zachowane z MyIsam czy poleci od 0 ?
Go to the top of the page
+Quote Post
mmmmmmm
post
Post #4





Grupa: Zarejestrowani
Postów: 1 421
Pomógł: 310
Dołączył: 18.04.2012

Ostrzeżenie: (0%)
-----


Cytat(Petre @ 3.01.2013, 11:11:51 ) *
co ciekawe, kiedy umieszczam AND zc.id_cech=2 AND pj.id_lang=1 w WHERE czas zapytania jest dłuższy niż, gdy owe pola wstawię w JOINACH

Bo wtedy są wykonywane ZUPEŁNIE INNE zapytania... Chyba nie masz do końca pojęcia jaka to różnica.
Go to the top of the page
+Quote Post
Sephirus
post
Post #5





Grupa: Zarejestrowani
Postów: 1 527
Pomógł: 438
Dołączył: 28.06.2011
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


Tak jak Ci pisałem to istotne by owarunkowanie tabel wiązanych było właśnie w joinach (IMG:style_emoticons/default/smile.gif) W WHERE trzymaj tylko to co dotyczy tabeli tej "pierwszej" do której joinujesz.

Sprawdziłeś indeksy czy są tak jak Ci pisałem? (dla wszytskich pól z WHERE, ON, ORDER - w sumie GROUP BY też)


Co do InnoDB... hmm na żywym organiźmie - tymbardziej sklepie bym tego nie robił raczej. Ogólnie najlepiej by było zatrzymać sklep, zrobić backup całej bazy, zrobić konwersje i przetestować. Ogólnie InnoDb jeśli nie poustawiasz kluczy obcych nie powinno nic popsuć na bazie która działała w MyISAM chyba że są tam używane zapytania typowe dla MyISAM jak wyciąganie statysyk tabel z tabeli systemowych - ale wątpie by ktoś na to wpadł patrząc na te zapytanka.

O ile wzrosła wydajność po samej zmianie na JOINy i poprawieniu INDEKSÓW podaj jakieś liczby cokolwiek.
Go to the top of the page
+Quote Post
mmmmmmm
post
Post #6





Grupa: Zarejestrowani
Postów: 1 421
Pomógł: 310
Dołączył: 18.04.2012

Ostrzeżenie: (0%)
-----


Cytat(Sephirus @ 3.01.2013, 11:53:49 ) *
Tak jak Ci pisałem to istotne by owarunkowanie tabel wiązanych było właśnie w joinach (IMG:style_emoticons/default/smile.gif) W WHERE trzymaj tylko to co dotyczy tabeli tej "pierwszej" do której joinujesz.

To też nie jest prawdą...
Jak znajdę czas, to dam przykład z pokazaniem różnic, bo to jest bez przykładu ciężko zrozumieć...
Go to the top of the page
+Quote Post
Sephirus
post
Post #7





Grupa: Zarejestrowani
Postów: 1 527
Pomógł: 438
Dołączył: 28.06.2011
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


@UP MySQL lubi robić duże tabelki tymczasowe jak go źle instruujesz - w momencie gdy w warunku złączenia określisz więcej danych (bardziej okroisz liczbę pasujących rekordów) tabela tymczasowa jest mniejsza a co za tym idzie sortowanie szybsze itd itp.

Chętnie jednak zobaczę przykład taki (oczywiście praktyczny) w którym będzie odwrotnie.

Oczywiście są przypadki gdy nie ma to różnicy (jeśli i tak i tak pobierane są wszystkie rekordy z tabeli złączanej) lub gdy jest to mniej wygodne - tak czy inaczej IMHO lepiej jest to pisać w ten sposób bo może się czasem nic nie zyska ale przynajmniej nie straci.
Go to the top of the page
+Quote Post
Petre
post
Post #8





Grupa: Zarejestrowani
Postów: 29
Pomógł: 0
Dołączył: 12.05.2012

Ostrzeżenie: (0%)
-----


Cytat(Sephirus @ 3.01.2013, 11:53:49 ) *
Tak jak Ci pisałem to istotne by owarunkowanie tabel wiązanych było właśnie w joinach (IMG:style_emoticons/default/smile.gif) W WHERE trzymaj tylko to co dotyczy tabeli tej "pierwszej" do której joinujesz.

Sprawdziłeś indeksy czy są tak jak Ci pisałem? (dla wszytskich pól z WHERE, ON, ORDER - w sumie GROUP BY też)

O ile wzrosła wydajność po samej zmianie na JOINy i poprawieniu INDEKSÓW podaj jakieś liczby cokolwiek.


przerobiłem zapytanie do takiej postaci :
  1. SELECT z.zdj, z.id_zest, z.symbol, zj.nazwa, zj.*,MAX(p.data_add) AS data_produkt, zc.wartosc,zc.id_cech
  2. FROM zestawy z
  3. JOIN zestawy_jezyki zj ON z.id_zest=zj.id_zest AND zj.id_lang=1
  4. JOIN produkty p ON p.id_zest=z.id_zest AND p.detal=1 AND p.stan=1
  5. JOIN kategorie_zestawy kz ON kz.id_zest=z.id_zest
  6. JOIN kategorie k ON kz.id_kat=k.id_kat
  7. LEFT JOIN produkty_jezyki pj ON p.id_prod=pj.id_prod AND pj.id_lang=1
  8. LEFT JOIN zestawy_cechy zc ON zc.id_zest=z.id_zest AND zc.id_cech=2
  9. WHERE z.stan=1 AND (kz.id_kat=3009 OR k.id_gr=3009)
  10. GROUP BY z.id_zest
  11. ORDER BY data_produkt DESC , zj.nazwa ASC


pozakładałem indexy na wszystkie z pól ON, WHERE, ORDER, GROUP BY

Testuję na "żywym" sklepie. Wyniki :
duration: od 5 - 11 s

Kiedy warunki zostawiam w WHERE wówczas czas jest nieznacznie krótszy.
Nie jest to jednoznaczne ponieważ ilość użytkowników w sklepie ciągle się zmienia więc i obciążenie serwera się waha.

Jest jeszcze jakaś opcja przetestowania bardziej miarodajna ?
Go to the top of the page
+Quote Post
phpion
post
Post #9





Grupa: Moderatorzy
Postów: 6 072
Pomógł: 861
Dołączył: 10.12.2003
Skąd: Dąbrowa Górnicza




Daj może wynik EXPLAINa. Na pierwszy rzut oka spojrzałbym czy są pozakładane indeksy na kolumnach, których używasz w złączeniach. Nie chodzi o indeks na każdej kolumnie z osobna, ale o indeks na N kolumnach, czyli np.:
  1. JOIN zestawy_jezyki zj ON z.id_zest=zj.id_zest AND zj.id_lang=1

tutaj wypadałoby dodać 1 indeks na obu kolumnach.

Zastanów się również czy nie ma możliwości pozbycia się tego grupowania. Ot tak pewnie nie, ale może warto dodać gdzieś dodatkową kolumnę przechowującą wartość MAX(p.data_add). Wartość kolumny można by aktualizować triggerem by działo się to automatycznie. No ale tutaj wkraczamy już w modyfikację struktury bazy więc teren robi się grząski (IMG:style_emoticons/default/wink.gif)
Go to the top of the page
+Quote Post
Sephirus
post
Post #10





Grupa: Zarejestrowani
Postów: 1 527
Pomógł: 438
Dołączył: 28.06.2011
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


Jeśli sklep ma duże obciążenie to możliwe, że zapytanie czeka na odblokowywanie tabel - zajrzyj w procesy - zobacz ile trwają - na czym wiszą (SHOW PROCESSLIST na przykład - lub poprzez PHPMyAdmin itp)

Jeśli wiszą na tym to InnoDB powinno znacznie pomóc.
Go to the top of the page
+Quote Post
Petre
post
Post #11





Grupa: Zarejestrowani
Postów: 29
Pomógł: 0
Dołączył: 12.05.2012

Ostrzeżenie: (0%)
-----


Cytat(Sephirus @ 3.01.2013, 13:47:51 ) *
Jeśli sklep ma duże obciążenie to możliwe, że zapytanie czeka na odblokowywanie tabel - zajrzyj w procesy - zobacz ile trwają - na czym wiszą (SHOW PROCESSLIST na przykład - lub poprzez PHPMyAdmin itp)

Jeśli wiszą na tym to InnoDB powinno znacznie pomóc.


Co widzę to często pojawia się COPYING TO TMP TABLE.

Cytat(Sephirus @ 3.01.2013, 13:47:51 ) *
Jeśli sklep ma duże obciążenie to możliwe, że zapytanie czeka na odblokowywanie tabel - zajrzyj w procesy - zobacz ile trwają - na czym wiszą (SHOW PROCESSLIST na przykład - lub poprzez PHPMyAdmin itp)

Jeśli wiszą na tym to InnoDB powinno znacznie pomóc.


Dostalem odpowiedz od adminow, ze wisi na tych COPYING TO TMP TABLE. Mowia, ze zwiekszyli takze buffor bazy pod to.

Jak widzicie rozbic to na mniejsze zapytania (tak jak pisalem wczesniej) ? Mniej JOINow, ale wiecej zabawy z odpowiednim selekcjonowaniem wynikow do zmiennych.

Go to the top of the page
+Quote Post
netmare
post
Post #12





Grupa: Zarejestrowani
Postów: 285
Pomógł: 37
Dołączył: 18.12.2007
Skąd: Łódź

Ostrzeżenie: (0%)
-----


Ciężki masz ten przykład. Nie znam za bardzo MySQL-a i nie wiem czy może go dociążać to dziwaczne grupowanie po mniejszej liczbie kolumn niż wyświetlana.
Poza tym LEFT JOIN dość spowalnia.

Do tego przenoszenie warunku pomiędzy WHERE a LEFT JOIN ma zgoła odmienne rezultaty a Ty przerzucasz to ot tak (IMG:style_emoticons/default/haha.gif)

Któryś z kolegów napisał żeby MySQL lubi być ograniczony na poziomie tworzenia złączeń więc przerzuć z.stan=1 do złączenia zj, a (kz.id_kat=3009 OR k.id_gr=3009) do złączenia k

Zastanów się czy ma być to tak: LEFT JOIN produkty_jezyki pj ON p.id_prod=pj.id_prod AND pj.id_lang=1 bo to jeśli to jest poprawne to zdaje mi się że wynik będzie identyczny jak to odrzucisz.

Jaką informację ma przedstawiać MAX(p.data_add) AS data_produkt ? Może da się podejść do rozwiązania problemu od innej strony? Bo na localu jak na warunki sklepu internetowego wysoki wydaje się czas 0.1 s.

Wklej EXPLAIN tego zapytania może ktoś zauważy coś ciekawego.
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
2 Użytkowników czyta ten temat (2 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Aktualny czas: 25.08.2025 - 06:44