Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Optymalizacja zapytania
Forum PHP.pl > Forum > Bazy danych > MySQL
TomASS
Cześć, mam takie oto zapytanie:

  1. SELECT
  2. DISTINCT T.NumerRef
  3. FROM
  4. m_transporty_miejsca AS TM LEFT JOIN
  5. m_transporty AS T ON (T.ID=TM.ID_transport)
  6. WHERE
  7. 1 AND
  8. (AnulowanyFE2='0' OR DateRealizacji>='2011-06-29') AND
  9. 1 AND
  10. T.Odleglosc>='0' AND
  11. T.Odleglosc<='99' AND
  12. T.ImportFE='1' AND
  13. ( T.ID_przewoznik='72' OR (STATUS='przedstawiony' AND 1) OR (Gielda='1' AND STATUS='gielda' AND 1) ) AND
  14. ( Miejsce='akt' OR (STATUS='zaksięgowane w SAP' AND DataDostarczenia IS NULL) )
  15. ORDER BY T.ID


Tabela m_transporty_miejsca 200 000 rekordów
Tabela m_transporty 100 000 rekordów
raczej małe ilości.

Zapytanie wykonuje się około 0,8s (średnia z 20pomiarów).
Jeśli usunę ostatni warunek w klauzuli WHERE:
  1. ( Miejsce='akt' OR (STATUS='zaksięgowane w SAP' AND DataDostarczenia IS NULL) )

a szczególnie
  1. OR (STATUS='zaksięgowane w SAP' AND DataDostarczenia IS NULL)

wówczas czas zapytania spada do 0,03 (średnia z 20pomiarów)

Indexy jakie min. mam pozakładane na tabelę m_transporty to:
ImportFE1: (ImportFE)
ImportFE2: (ImportFE+Miejsce)
ImportFE3: (ImportFE+Status+DataDostarczenia)

EXPLAIN tego zapytania wskazuje, że użyty jest indeks "ImportFE1"

Proszę o pomoc w optymalizacji.
uupah5
przydałoby się dodać do wszystkich pól aliasy tabel, skoro już dajesz to do publicznej analizy.
pole status występuje kilkukrotnie z czego ostatni raz jest wzajemnie wykluczający się z przedostatnim, więc może jednak przekonstruować to zapytanie tak, aby pole status występowało samodzielnie i nadrzędnie, czyli coś w stylu:
status = a and (....)
or
status = b and (...)
oczywiście to szkic, bo to Ty te dane rozumiesz, więc przebudowanie warunków zostawiam Tobie.
jeśli jest to wykonalne, to dać potem index na status. btw to musi być varchar? oraz rozumiem struktura jest świadomie zdenormalizowana?

a na marginesie, 0.8s to jest faktycznie problem?
rollen
TomASS ma rację spróbuj jakoś przebudować tę część ze STATUS, bo jest całkowicie nieczytelana...

Wskazałeś też, że użyty został indeks ImportFE1: (ImportFE). Nie wiem jakie dane masz w columnie ImportFE, ale wydaje mi się, że to jakiś status z ograniczony do kilku wartości. Taki indeks staje ję mało selektywny. Proponuję go rozszerzyć o T.Odległość.
Czyli ImportFE1: (ImportFE, Odleglosc).
Inne zapytania kożystające z tego indeksu nie ucierpią, a to zapytanie dostanie dodatkowego kopa (o ile są wartości w kolumnie Odleglosc wyższe niż w podanym warunku zakresu[0, 99] )

Cytat(uupah5 @ 6.07.2011, 16:09:10 ) *
a na marginesie, 0.8s to jest faktycznie problem?


Przy tak małej ilości danych w bazie to jest wieczność...
TomASS


Cytat(uupah5 @ 6.07.2011, 16:09:10 ) *
pole status występuje kilkukrotnie z czego ostatni raz jest wzajemnie wykluczający się z przedostatnim

Nie wyklucza się - w obydwu przypadkach jest pole status + OR

Cytat(uupah5 @ 6.07.2011, 16:09:10 ) *
więc może jednak przekonstruować to zapytanie tak, aby pole status występowało samodzielnie i nadrzędnie, czyli coś w stylu:
status = a and (....)
or
status = b and (...)

Teraz zrobiłem tak:
  1. SELECT DISTINCT T.NumerRef, T.DataWyjazdu, T.ID_grupa_transportowa ,
  2. T.Pojazd, T.Datetime_do_kiedy_cena_tradycyjna, T.AnulowanyFE2,
  3. T.HD, T.IloscTowaruRzeczywista, T.Multistop, T.DataDostarczenia,
  4. PonaglenieDostarczenia, PonaglenieAwizacji,
  5. TIMESTAMPDIFF(MINUTE, NOW(), T.DateRealizacji) AS CzasDoKoncaRealizacji,
  6. T.PotwierdzeniePrzyjeciaZmian, T.IloscTowaru, T.Odleglosc, T.TerminWaznosci,
  7. T.GodzinaRealizacji, (DataDodaniaNaGielde + INTERVAL TerminWaznosci MINUTE) AS LicytacjaDo,
  8. T.Gielda, T.ID, T.DateRealizacji, T.STATUS, T.Numer, RodzajCiezarowki, ROUND(Cena,2) AS Cena,
  9. Waluta
  10. FROM
  11. m_transporty_miejsca AS TM LEFT JOIN
  12. m_transporty AS T ON (T.ID=TM.ID_transport)
  13. WHERE
  14. 1 AND
  15. (T.AnulowanyFE2='0' OR T.DateRealizacji>='2011-06-30') AND
  16. 1 AND
  17. T.Odleglosc>='9' AND
  18. T.Odleglosc<='102' AND
  19. T.ImportFE='1' AND
  20. ((T.Miejsce='akt' AND ((T.STATUS='przedstawiony' AND 1)
  21. OR (T.STATUS='gielda' AND T.Gielda='1' AND 1)
  22. OR T.ID_przewoznik='72'))
  23. OR (T.STATUS='zaksięgowane w SAP' AND T.DataDostarczenia IS NULL AND T.ID_przewoznik='72'))
  24. ORDER BY T.ID


i dalej nic.
Ze statusem to kłopot taki, że chcę wyswietlać rekordy w różnych statusach, ale przy każdym statusie muszą być spełnione dodakowe (za każdym statusem różne) warunki. Wystarczy, że zajdzie któryś z tych warunków:

1. T.Status='gielda' AND T.Gielda='1' AND 1 AND T.Miejsce='akt
2. T.Status='przedstawiony' AND 1 AND T.Miejsce='akt
3. T.ID_przewoznik='72 AND T.Miejsce='akt (bez różnicy na status)
4. T.Status='zaksięgowane w SAP' AND T.DataDostarczenia IS NULL AND T.ID_przewoznik='72'



Cytat(uupah5 @ 6.07.2011, 16:09:10 ) *
jeśli jest to wykonalne, to dać potem index na status. btw to musi być varchar?

Status ma index - tak jest varchar
Cytat(uupah5 @ 6.07.2011, 16:09:10 ) *
oraz rozumiem struktura jest świadomie zdenormalizowana?

Przypadek sad.gif
Cytat(uupah5 @ 6.07.2011, 16:09:10 ) *
a na marginesie, 0.8s to jest faktycznie problem?

Bardzo długo sad.gif 12razy dłużej niż bym chciał



Cytat(rollen @ 6.07.2011, 21:14:40 ) *
TomASS ma rację spróbuj jakoś przebudować tę część ze STATUS, bo jest całkowicie nieczytelana...

tylko jak ? Moja wyobraźnia chyba tego nie ogarnia już:/
Cytat(rollen @ 6.07.2011, 21:14:40 ) *
Wskazałeś też, że użyty został indeks ImportFE1: (ImportFE). Nie wiem jakie dane masz w columnie ImportFE, ale wydaje mi się, że to jakiś status z ograniczony do kilku wartości.

dwie możliwości - 0 oraz 1
Cytat(rollen @ 6.07.2011, 21:14:40 ) *
Taki indeks staje ję mało selektywny. Proponuję go rozszerzyć o T.Odległość.

pole odegłość to jedna z 20 możliwośći

dzięki chłopaki - pomóżcie jeszcze troszkę, może coś się uda.
uupah5
patrząc tylko w kod, bez danych i na sucho. sprawdź sobie czy nie pominąłem jakiegoś warunku.

  1. (...)
  2. T.ImportFE='1' AND
  3. (
  4. (T.STATUS='zaksięgowane w SAP' AND (T.DataDostarczenia IS NULL AND T.ID_przewoznik='72'))
  5. OR
  6. (T.STATUS='przedstawiony' AND (T.Miejsce='akt'))
  7. OR
  8. (T.STATUS='gielda' AND (T.Miejsce='akt' AND T.Gielda='1'))
  9. OR
  10. (T.Miejsce='akt' AND T.ID_przewoznik='72')
  11. )


do tego Status jednak bym znormalizował albo przynajmniej zamienił na small int i oczywiśce zaindeksował.
ta sama uwaga do Miejsce - to też jakiś słownik stanów, zdenormalizowany i na varchar?
Gielda to pole binarne czy też varchar? znasz tylko jeden typ danych?wink.gif

mam nadzieję, że moja propozycja da jakiś efekt. bez danych i struktury tabel opieram się tylko na algebrze boola;)

ps pytałem się czy czas jest krytyczny, bo nie zawsze jest. przykładowo, mógł być to raport co się generuje w nocy.
TomASS
Dziękuję za pomoc - mam teraz zapytanie tak:
  1. SELECT DISTINCT T.NumerRef
  2. FROM
  3. m_transporty_miejsca AS TM LEFT JOIN
  4. m_transporty AS T ON (T.ID=TM.ID_transport)
  5. WHERE
  6. 1 AND
  7. (AnulowanyFE2='0' OR DateRealizacji>='2011-06-30') AND
  8. 1 AND
  9. T.Odleglosc>='0' AND
  10. T.Odleglosc<='99' AND
  11. T.ImportFE='1' AND
  12. (
  13. (T.STATUS='zaksięgowane w SAP' AND (T.DataDostarczenia IS NULL AND T.ID_przewoznik='72'))
  14. OR
  15. (T.STATUS='przedstawiony' AND (T.Miejsce='akt'))
  16. OR
  17. (T.STATUS='gielda' AND (T.Miejsce='akt' AND T.Gielda='1'))
  18. OR
  19. (T.Miejsce='akt' AND T.ID_przewoznik='72')
  20. )
  21. ORDER BY T.ID

I nie zanotowałem, żadnego przyspieszenia sad.gif
EXPLAIN wskazuje, że używany nadal jest tylko indeks ImportFE1: (ImportFE).
Pole status ma około 30 wartości a jego zmiana będzie b. kłopotliwa - wolałbym tego uniknąć.

Cytat
Gielda to pole binarne czy też varchar? znasz tylko jeden typ danych?

Gielda - ENUM 0,1,2,3
nospor
STATUS powinno być ENUM a nie smallint. Wówczas nie trzeba będzie zmieniać zapytań, które zapewne się przewalają przez całą aplikację.

Cytat
Gielda - ENUM 0,1,2,3
Trochę dziwne używać ENUM dla wartosci numerycznych wink.gif
uupah5
cytat z mauala mysql:
we strongly recommend that you do not use numbers as enumeration values

poza tym, wrzuć w goole "why enum is evil" albo poszukaj na forum, bo chyba już kiedyś zapodawałem linka z art. na ten temat

a co do braku efektu po zmianach - status jest zaindeksowane?

ps piszesz, że chcesz uniknąć przeróbek. obym był złym prorokiem, ale moim zdaniem źle zaprojektowana baza danych da Ci się we znaki jeszcze nie raz

TomASS
Cytat(nospor @ 7.07.2011, 11:49:03 ) *
STATUS powinno być ENUM a nie smallint. Wówczas nie trzeba będzie zmieniać zapytań, które zapewne się przewalają przez całą aplikację.

Trochę dziwne używać ENUM dla wartosci numerycznych wink.gif

Sugerujesz, że lepiej zmienić na smallint?
nospor
Gielda? Mówię jedynie że to jest dziwne wink.gif To tak jakbym używał większej szklanki do trzymania w niej mniejszej szklanki smile.gif
TomASS
Nic nie dała zmiana pola Status na ENUM sad.gif
ano
Myślę, że zmiana STATUS i miejsce na pola TINYINT (unsigned przechowuje liczby do 255 więc w zupełności powinno wystarczyć do tych celów) rozwiąże Twój problem. O wiele cięższe jest porównanie stringów niż intów i z tym każdy musi się zgodzić.
W ogóle, wydaje mi się, że to, że status jest przechowywany w formie stringa świadczy o tym, że ktoś źle zaprojektował tą bazę. (jeśli dobrze sobie to wyobrażam, to STATUS to nie jest dowolny string, a jeden spośród listy możliwych statusów - więc idealnie int się do tego nadaje.

Co do pola miejsce to zależy co tam przechowujesz, jak działa na tej samej zasadzie jak "status" to też można by było przerobić na inta.

To pociąga ze sobą oczywiście zmiany w kodzie Twojej aplikacji. Jak wcześniej po prostu wyświetlałeś status to tutaj będziesz musiał robić coś w stylu $status[ID_STATUSU_POBRANE_Z_DB]. i $status = array('blablabla','blabla');
ale myślę, że o takich 'oczywistościach' już nie musze wspominać? wink.gif
everth
Abstrahując od powyższych wypowiedzi - zwróciłeś uwagę na fakt że warunek który drastycznie zmienia czas wykonania różni się czymś od pozostałych? Bo jak dla mnie wyróżnia go jedno - klauzula IS(NOT) NULL. Wcześniej wykonujesz LEFT JOIN - złączenie zewnętrzne. Te złączenia nie dają się tak łatwo optymalizować jak złączenia wewnętrzne (INNER JOIN) zwłaszcza że tutaj dokonujesz porównania na wartości NULL (która jest tworzona przez LEFT JOIN). Podejrzewam że z tym warunkiem baza musi przemielić znacznie więcej rekordów niż bez niego - i to pewnie wpływa na czas wykonania. Poeksperymentuj jak to wygląda bez porównań do NULLa (lub spróbuj je jakoś wyeliminować).
TomASS
Cytat
Abstrahując od powyższych wypowiedzi - zwróciłeś uwagę na fakt że warunek który drastycznie zmienia czas wykonania różni się czymś od pozostałych? Bo jak dla mnie wyróżnia go jedno - klauzula IS(NOT) NULL. Wcześniej wykonujesz LEFT JOIN - złączenie zewnętrzne. Te złączenia nie dają się tak łatwo optymalizować jak złączenia wewnętrzne (INNER JOIN) zwłaszcza że tutaj dokonujesz porównania na wartości NULL (która jest tworzona przez LEFT JOIN). Podejrzewam że z tym warunkiem baza musi przemielić znacznie więcej rekordów niż bez niego - i to pewnie wpływa na czas wykonania. Poeksperymentuj jak to wygląda bez porównań do NULLa (lub spróbuj je jakoś wyeliminować).

Dzięki, całkowite usunięcie warunki z IS NULL nic nie dało w przyspieszeniu zapytania sad.gif


Cytat
rochę dziwne używać ENUM dla wartosci numerycznych

Czy dla pól, które mogą przyjąć tylko wartości 0/1/2/3 - lepiej jest użyć (small)int czy Enum (0/1/2/3)?
Czy konwersja ENUM->SMALLINT nie sprawi mi kłopotu (utrata danych/kłopoty w zapytaniach?)

dzięki.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.