Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Powolne przeglądanie produktów
Forum PHP.pl > Forum > Bazy danych > MySQL
Walian
Witam,

Pracuję nad pewnym serwisem internetowym (niestety nie wolno mi podawać nazwy ani adresu), który zaczyna już dość mocno zwalniać. W chwili obecnej problem stanowią dwie tabele:
- tabela produktów - MyISAM, ponad 700 tyś rekordów, ponad 180 MB.
- tabela ofert do produktów - MyISAM, ponad 1 125 000 rekordów, ponad 230 MB.

Do rzeczy - problem stanowi już samo przeglądanie produktów.
  1. SELECT
  2. SQL_NO_CACHE
  3. SQL_CALC_FOUND_ROWS
  4. `p`.`produkt_id`,
  5. `p`.`name`,
  6. `p`.`katalog`,
  7. `p`.`pod_katalog`,
  8. `p`.`category_id`,
  9. `o`.`user_id`,
  10. `o`.`cena`,
  11. `o`.`id` AS `id_offer`
  12. FROM
  13. `produkty` `p`
  14. INNER JOIN
  15. `oferty` `o`
  16. ON
  17. `o`.`produkt_id`=`p`.`produkt_id`
  18. LEFT JOIN
  19. `uzytkownicy` `u`
  20. ON
  21. `u`.`user_id`=`o`.`user_id`
  22. WHERE
  23. `u`.`aktywny`=1 ; Tu waskie gardlo...
  24. ; W chwili obecnej wszyscy maja "1", ale TO sie kiedys zmieni...
  25. AND
  26. `p`.`category_id` IN (tutaj 176 numerow (cale drzewko) kategorii)
  27. AND ; Od tego momentu rozne filtry:
  28. `o`.`cena` BETWEEN 1 AND 99999
  29. ORDER BY
  30. `p`.`name` ASC ; Zamiast tego moze sie pojawic sortowanie wg ceny
  31. LIMIT 0,20

Cytat
20 rows in set (9.70 sec)

Bez SQL_CALC_FOUND_ROWS:
Cytat
20 rows in set (6.63 sec)

To samo zapytanie, ale bez wyciągania danych - zamiast tego COUNT(*):
Cytat
20 rows in set (6.71 sec)

Teraz wracając do początku:
Bez "`u`.`aktywny`=1":
Cytat
20 rows in set (1.86 sec)

Bez "`u`.`aktywny`=1" oraz bez "ORDER BY `p`.`name` ASC":
Cytat
20 rows in set (1.16 sec)

Bez "`u`.`aktywny`=1" oraz bez "`o`.`cena` BETWEEN 1 AND 99999" oraz bez "ORDER BY `p`.`name` ASC":
Cytat
20 rows in set (1.07 sec)

Ostatnie zapytanie, ale bez SQL_CALC_FOUND_ROWS:
Cytat
20 rows in set (0.00 sec)


Serwis stoi na serwerze dedykowanym...

Jak przyspieszyć to zapytanie? Nie mogę zrezygnować z tych warunków WHERE, ale może da radę jakoś inaczej zapytanie skonstruować?
Przejście na InnoDB pomoże czy rozwiązania trzeba szukać gdzie indziej?
erix
A nie możesz po prostu wywołać dwóch zapytań? Jedno count z tymi warunkami, drugie właściwe? Myślę, że szybciej będzie.
nospor
zrob z tego zapytania EXPLAIN i zobacz na czym muli.
Zapewne musisz pozakładać własciwe indexy
Walian
Cytat("erix")
A nie możesz po prostu wywołać dwóch zapytań? Jedno count z tymi warunkami, drugie właściwe? Myślę, że szybciej będzie.

Cytat("Walian")
To samo zapytanie, ale bez wyciągania danych - zamiast tego COUNT(*):
Cytat
20 rows in set (6.71 sec)

Jak widzisz jest jeszcze gorzej, już bardziej się opłaca z SQL_CALC_FOUND_ROWS.

EXPLAIN EXTENDED:
Cytat
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE u ALL PRIMARY NULL NULL NULL 892 Using where; Using temporary; Using filesort
1 SIMPLE o ref produkt_id,user_id,cena user_id 3 u.user_id 19 Using where
1 SIMPLE a eq_ref PRIMARY,category_id PRIMARY 4 o.produkt_id 1 Using where

Próbowałem już wcześniej z różnymi indeksami, z pozbywaniem się filesort-a, nawet z FORCE INDEX, no ale nie wiem jak sobie z tym poradzić.
erix
Huh, nie zauważyłem...

Pokaż lepiej
  1. SHOW CREATE TABLE


A jeśli chodzi o rozwiązanie - musisz każdorazowo odpytywać DB? Nie myślałeś o cache po stronie klienta? (nie mylić z cache demona)
Walian
Ale jak niby taki cache zrobić? Nie widzę tu sensu, skoro najmniejsza zmiana w filtrze, przejście do innej kategorii, czy wyszukiwanie wg jakieś frazy zmienia już postać i ilość wyników. Po stronie serwera też nie ma sensu bo powstanie olbrzymia ilość plików cache. Jeśli do tego uwzględnić fakt, że ciągle są dodawane lub aktualizowane produkty to liczba "kombinacji" jest olbrzymia.

  1. SHOW CREATE TABLE `uzytkownicy`;
  2. CREATE TABLE `uzytkownicy` (
  3. `user_id` int(11) NOT NULL AUTO_INCREMENT,
  4. `aktywny` int(11) NOT NULL DEFAULT '1',
  5. PRIMARY KEY (`user_id`),
  6. FULLTEXT KEY `nazwa_sklepu` (`nazwa_sklepu`),
  7. FULLTEXT KEY `city` (`city`)
  8. ) ENGINE=MyISAM AUTO_INCREMENT=100962 DEFAULT CHARSET=latin2 PACK_KEYS=0
  9. -- Indeks na polu "aktywny" nic nie daje, bo w kazdym rekordzie jest "1", przynajmniej na razie
  10.  
  11. SHOW CREATE TABLE `produkty`;
  12. CREATE TABLE `produkty` (
  13. `produkt_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  14. `name` varchar(255) NOT NULL,
  15. `category_id` smallint(11) NOT NULL DEFAULT '0',
  16. `katalog` tinyint(4) NOT NULL DEFAULT '0',
  17. `pod_katalog` tinyint(4) NOT NULL DEFAULT '0',
  18. PRIMARY KEY (`produkt_id`),
  19. KEY `products_name` (`active`,`name`),
  20. KEY `category_id` (`category_id`),
  21. FULLTEXT KEY `name` (`name`)
  22. ) ENGINE=MyISAM AUTO_INCREMENT=911612 DEFAULT CHARSET=utf8 PACK_KEYS=0
  23.  
  24. SHOW CREATE TABLE `oferty`;
  25. CREATE TABLE `oferty` (
  26. `id` int(11) NOT NULL AUTO_INCREMENT,
  27. `user_id` mediumint(10) NOT NULL DEFAULT '0',
  28. `cena` double(16,2) NOT NULL DEFAULT '0.00',
  29. `nazwa_prod` varchar(255) NOT NULL,
  30. `offer_id` bigint(24) UNSIGNED NOT NULL,
  31. `produkt_id` int(10) UNSIGNED NOT NULL,
  32. PRIMARY KEY (`id`),
  33. KEY `produkt_id` (`produkt_id`),
  34. KEY `user_id` (`user_id`),
  35. KEY `cena` (`cena`)
  36. ) ENGINE=MyISAM AUTO_INCREMENT=1357689 DEFAULT CHARSET=utf8


Wyciąłem kolumny, które nie biorą udziału w zapytaniu. Zresztą jest ich od cholery...
Wiem, wiem - tragedia... ale muszę to jakoś przyspieszyć ;/
erix
Cytat
Wyciąłem kolumny, które nie biorą udziału w zapytaniu. Zresztą jest ich od cholery...

Hmm, a nie myślałeś o rozbiciu tego na wiele tabel? Np. opisy w osobnej, łączone relacją 1-1.

Jeśli są kolumny typu text/varchar - przyspieszy to wyszukiwanie.
wookieb
Daj całe create table (nie usuwaj "zbędnych" kolumn) bo nie mam jak tego przetestować na bazie aby w troszkę mniejszym stopniu pobawić się z optymalizacją.

Cytat
Jeśli są kolumny typu text/varchar - przyspieszy to wyszukiwanie.

Nie ma to żadnego znaczenia w tym zapytaniu
Walian
Gdybym projektował ten serwis od początku to tak właśnie bym zrobił bo upraszcza to również eksport danej tabeli, czy jej przywracanie.
W sumie najwięcej kolumn (39, większość to VARCHAR) jest w tabeli userów, tabele produktów i ofert akurat nie mają ich dużo.
Niestety musiałbym napisać skrypt, który to wszystko rozdzieli, a potem zatrzymać serwis na czas wszystkich operacji. A nie chcę robić tego wszystkiego jeśli nie mam gwarancji, że to da cokolwiek. Tym bardziej, że dopiero przenieśliśmy się na dedyka, co zajęło masę czasu.

Myślisz, że na pewno się opłaci? W sumie i tak musiałbym poczekać z tym do poniedziałku, aż wróci drugi programista. Do tego czasu szukam różnych, alternatywnych rozwiązań.

EDIT: To wyżej było do erixa, spóźniłem się z postem smile.gif
wookieb - zaraz zapodam.
kaminskp
Sugeruję zastosowanie AJAX on szybko dłubie w danych i zwraca wyniki.
erix
Cytat
Nie ma to żadnego znaczenia w tym zapytaniu

Czy ja wiem? Jeśli są kolumny różnej długości, to nie ma możliwości odczytu sekwencyjnego (paczkami o stałej długości), tylko trzeba każdorazowo tę długość sprawdzać i wtedy skakać...

~kaminskp - tiaa, tylko że w poruszanym problemie nie ma jak wykonać jakiegokolwiek JS...
Walian
  1. CREATE TABLE `produkty` (
  2. `produkt_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  3. `name` varchar(255) NOT NULL,
  4. `category_id` smallint(11) NOT NULL DEFAULT '0',
  5. `active` tinyint(4) NOT NULL DEFAULT '0',
  6. `poprawiony` tinyint(4) NOT NULL DEFAULT '0',
  7. `katalog` tinyint(4) NOT NULL DEFAULT '0',
  8. `pod_katalog` tinyint(4) NOT NULL DEFAULT '0',
  9. PRIMARY KEY (`produkt_id`),
  10. KEY `products_name` (`active`,`name`),
  11. KEY `category_id` (`category_id`),
  12. FULLTEXT KEY `name` (`name`)
  13. ) ENGINE=MyISAM AUTO_INCREMENT=913300 DEFAULT CHARSET=utf8 PACK_KEYS=0


  1. CREATE TABLE `oferty` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `user_id` mediumint(10) NOT NULL DEFAULT '0',
  4. `cena` double(16,2) NOT NULL DEFAULT '0.00',
  5. `nazwa_prod` varchar(255) NOT NULL,
  6. `link` varchar(255) NOT NULL,
  7. `offer_id` bigint(24) UNSIGNED NOT NULL,
  8. `produkt_id` int(10) UNSIGNED NOT NULL,
  9. `aktualizacja` int(8) NOT NULL,
  10. PRIMARY KEY (`id`),
  11. KEY `produkt_id` (`produkt_id`),
  12. KEY `user_id` (`user_id`),
  13. KEY `cena` (`cena`)
  14. ) ENGINE=MyISAM AUTO_INCREMENT=1359539 DEFAULT CHARSET=utf8


A teraz czas na MATRIX:
  1. CREATE TABLE `uzytkownicy` (
  2. `user_id` int(11) NOT NULL AUTO_INCREMENT,
  3. `name` varchar(100) character SET utf8 NOT NULL,
  4. `email` varchar(255) character SET utf8 NOT NULL,
  5. `birthdate` date NOT NULL DEFAULT '0000-00-00',
  6. `address` varchar(100) character SET utf8 NOT NULL,
  7. `city` varchar(50) character SET utf8 collate utf8_polish_ci NOT NULL,
  8. `state` varchar(100) character SET utf8 NOT NULL,
  9. `country` varchar(100) character SET utf8 NOT NULL,
  10. `zip_code` varchar(30) NOT NULL DEFAULT '',
  11. `phone` varchar(30) NOT NULL DEFAULT '',
  12. `username` varchar(255) NOT NULL DEFAULT '',
  13. `password` varchar(32) NOT NULL DEFAULT '',
  14. `active` tinyint(4) NOT NULL DEFAULT '0',
  15. `zdjecie_logo` varchar(255) NOT NULL,
  16. `xml_link` varchar(225) NOT NULL,
  17. `xml_nazwa` varchar(50) NOT NULL,
  18. `pp` int(3) NOT NULL DEFAULT '0',
  19. `opis_problem` varchar(255) character SET utf8 NOT NULL,
  20. `payment_status` varchar(20) NOT NULL DEFAULT '',
  21. `automat` int(11) NOT NULL DEFAULT '0',
  22. `poprawny_plik` int(3) NOT NULL DEFAULT '0',
  23. `newsletter` tinyint(4) NOT NULL DEFAULT '0',
  24. `reg_date` int(11) NOT NULL DEFAULT '0',
  25. `mail_activated` tinyint(4) NOT NULL DEFAULT '0',
  26. `lang` varchar(255) NOT NULL DEFAULT 'english',
  27. `preferred_seller` tinyint(4) NOT NULL DEFAULT '0',
  28. `promowanie_sklepu` tinyint(4) NOT NULL DEFAULT '0',
  29. `birthdate_year` int(11) NOT NULL DEFAULT '0',
  30. `referred_by` varchar(200) NOT NULL DEFAULT '',
  31. `payment_mode` tinyint(1) DEFAULT '0',
  32. `ilosc_prod` int(11) DEFAULT '0',
  33. `nazwa_sklepu` varchar(100) character SET utf8 DEFAULT NULL,
  34. `tax_account_type` tinyint(4) NOT NULL DEFAULT '0',
  35. `salt` char(3) NOT NULL DEFAULT '',
  36. `approved` tinyint(4) NOT NULL DEFAULT '0',
  37. `aktualizacja` text NOT NULL,
  38. `prom_logo` int(3) NOT NULL DEFAULT '0',
  39. `akceptacja_promowanie` int(2) NOT NULL,
  40. `aktywny` int(11) NOT NULL DEFAULT '1',
  41. PRIMARY KEY (`user_id`),
  42. KEY `shop_active` (`active`,`user_id`),
  43. KEY `stores_list` (`active`),
  44. KEY `acc_overdue_users` (`payment_mode`,`reg_date`),
  45. KEY `active_users` (`active`,`reg_date`,`approved`),
  46. KEY `users_tax_acc_type` (`tax_account_type`,`reg_date`),
  47. KEY `users_tax_exempt` (`promowanie_sklepu`,`reg_date`),
  48. KEY `active` (`active`),
  49. FULLTEXT KEY `nazwa_sklepu` (`nazwa_sklepu`),
  50. FULLTEXT KEY `city` (`city`)
  51. ) ENGINE=MyISAM AUTO_INCREMENT=100962 DEFAULT CHARSET=latin2 PACK_KEYS=0


Pewnie kilka kolumn nie jest w ogóle używanych, to się je prędzej czy później wywali.
wookieb
Cytat(kaminskp @ 17.08.2010, 13:11:49 ) *
Sugeruję zastosowanie AJAX on szybko dłubie w danych i zwraca wyniki.

Idź do kąta, zastanów się nad swoim postępowaniem i nie gadaj głupot.
erix
Na próbę - przenieś kolumny varchar/text (tyle, ile się da) do osobnej tabeli, np. userMeta zostawiając pozostałe. Oczywiście nazwa użytkownika i login trzeba zostawić.

I teraz zobacz, ile czasu zajmie wyciągnięcie z tej pierwszej, z atrybutem aktywny=1. Podejrzewam, że przyspieszy. Wtedy JOIN z drugą tabelą (relacja 1-1 po np. ID użytkownika) i pokaż, jak wychodzi czas.

Nie siedzę aż tak mocno w bazach, więc moje myślenie może być błędne (niech kto poprawi, jeśli tak jest), ale patrząc na logikę działania wydaje mi się całkiem sensowne.

Najlepiej niech zweryfikują to fakty. winksmiley.jpg
wookieb
Niestety MyIsam słabo spisuje się w operacjach złączenia (wynika to między innymi z tego powodu, że nie przechowuje głównego indeksu tabeli w kluczach tak jak INNODB).
Po próbie zmiany myisam na innodb złączenia odrazu zaczęły wykorzystywać klucze.
Próbuję "conieco" zoptymalizować tabele i wynikami pochwale się jak tylko uda mi się coś zrobić aczkolwiek zastanów się na przyszłość czy nie lepiej przejśc na INNODB i do wyszukiwania wykorzystać sphinxa bądź inne systemy wyszukiwania (w zastępstwie FULLTEXT).
Walian
Stworzyłem nawet nieco mniejszą tabelkę dla testu:
  1. CREATE TABLE `uzytkownicy2` (
  2. `user_id` int(11) NOT NULL AUTO_INCREMENT,
  3. `state` varchar(100) character SET utf8 NOT NULL,
  4. `nazwa_sklepu` varchar(100) character SET utf8 DEFAULT NULL,
  5. `aktywny` int(11) NOT NULL DEFAULT '1',
  6. PRIMARY KEY (`user_id`),
  7. FULLTEXT KEY `nazwa_sklepu` (`nazwa_sklepu`)
  8. ) ENGINE=MyISAM AUTO_INCREMENT=100962 DEFAULT CHARSET=latin2 PACK_KEYS=0

Niestety ZERO przyspieszenia. A nawet działa nieco wolniej, choć to może być przypadek.

wookieb - ja zawsze byłem za InnoDB, ale jak wspominałem - nie ja zacząłem projektować ten serwis. Co do Sphinxa - nigdy z niego nie korzystałem, tak więc nie znam się na nim, wyczytałem jedynie mnóstwo pozytywnych opinii na jego temat, także kiedyś na pewno się nim pobawię, ale chyba nie w tym serwisie, bo raczej nie będzie na to czasu.
dr_bonzo
Hmm, indeksy na kolumnach zlaczen są.

Robisz LEFT JOINA do userów, a potem WHERE user.is_active = 1.
Czyli wlasciwie LEFT JOIN ci nie potrzebny - bo jesli nie znajdize usera to kolumny 'userowe' beda mialy wszedzie NULLe a NULL =?= true zawsze jest NIEspelnione.

Po tej zmianie EXPLAIN dla tabeli userow uzywa u mnie klucza glownego zamiast robic full scan'a.
-----
Przed
Kod
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE u ALL PRIMARY NULL NULL NULL 892 Using where; Using temporary; Using filesort
1 SIMPLE o ref produkt_id,user_id,cena user_id 3 u.user_id 19 Using where
1 SIMPLE a eq_ref PRIMARY,category_id PRIMARY 4 o.produkt_id 1 Using where
// produkty ma alias 'a' questionmark.gif?

Kod
id    select_type    table    type    possible_keys    key    key_len    ref    rows    Extra    
1    SIMPLE    u    index    PRIMARY    PRIMARY    4    NULL    12    Using index; Using temporary; Using filesort
1    SIMPLE    o    ref    produkt_id,user_id,cena    user_id    3    test.u.user_id    15    Using where
1    SIMPLE    p    eq_ref    PRIMARY,category_id    PRIMARY    4    test.o.produkt_id    1    Using where



jako że danych mam neiwiele (180 ofert) to czasów nie jestem w stanie podać.
Walian
Nie kumam - jeśli nie LEFT JOIN to co?
Cytat
bo jesli nie znajdize usera to kolumny 'userowe' beda mialy wszedzie NULLe a NULL =?= true zawsze jest NIEspelnione.

Sprawdziłem tak:
  1. SELECT SQL_NO_CACHE COUNT(*) FROM
  2. (
  3. SELECT SQL_NO_CACHE `p`.`produkt_id`,`p`.`name`,`p`.`katalog`,`p`.`pod_katalog`,`p`.`category_id`,`o`.`user_id`,`o`.`cena`,`o`.`id` AS id_offer,`u`.`aktywny` FROM `produkty` `p` INNER JOIN `oferty` `o` ON `o`.`auction_id`=`p`.`auction_id` LEFT JOIN `uzytkownicy` `u` ON `u`.`user_id`=`o`.`user_id` WHERE `u`.`aktywny`=1 AND `p`.`category_id` IN (numerki) AND `o`.`cena` BETWEEN 1 AND 99999 ORDER BY `p`.`name` ASC LIMIT 0,20
  4. ) AS `r`
  5. WHERE
  6. aktywny = NULL;

Zwróciło 0.
Chyba, że źle zrozumiałem lub źle sprawdziłem.

Nie wiem, co żeś za zapytanie zmajstrował, że Ci takie EXPLAIN wyszło.
VegetaSSJ
a wiedziałeś o tym że:

  1. `p`.`category_id` IN (tutaj 176 numerow (cale drzewko) kategorii)


rozbijane jest na:

  1. AND (`p`.`category_id` = nr1 OR `p`.`category_id` = nr2 OR `p`.`category_id` = nr3 OR ... `p`.`category_id` = nr176)


jeżeli masz drzewko kategorii oparte na left-right lepiej będzie zastąpić to beetwenem z podaniem odpwiednich wartści roota drzewka.
dr_bonzo
Cytat
Nie kumam - jeśli nie LEFT JOIN to co?

To INNER JOIN (zapomnialem dopisac)

i

Cytat
...WHERE
aktywny = NULL;


o czyms zapomniales:
  1. SELECT NULL = NULL, 1 = NULL, 0 = NULL, 0 = 1, 0 = 0


---
no i SQLka szukajaca

  1. SELECT
  2. SQL_NO_CACHE SQL_CALC_FOUND_ROWS `p`.`produkt_id`, `p`.`name`, `p`.`katalog`, `p`.`pod_katalog`, `p`.`category_id`, `o`.`user_id`, `o`.`cena`, `o`.`id` AS `id_offer`, u.user_id
  3. FROM `produkty` `p`
  4. INNER JOIN `oferty` `o` ON `o`.`produkt_id`=`p`.`produkt_id`
  5. INNER JOIN `uzytkownicy` `u` ON `u`.`user_id`=`o`.`user_id`
  6. WHERE
  7. -- `u`.`aktywny`=1 AND
  8. `p`.`category_id` IN (2, 23, 32, 35, 48, 49, 52, 65, 66, 77, 79, 89, 98) AND -- 180 IDekow kategorii
  9. `o`.`cena` BETWEEN 1 AND 99999
  10. ORDER BY
  11. `p`.`name` ASC

LIMIT 0,20
Walian
Oczywiście, że wiem o tym, sęk w tym, iż numery nie są jednym przedziałem - to id kategorii, które mogą się zmieniać.
I w praktyce rzeczywiście są to różne wartości.
Jak widać BETWEEN znam, bo używałem dla ceny, gdybym mógł użyć również tutaj to bym tak zrobił.

dr_bonzo: Zmiana z LEFT na INNER nic u mnie nie zmieniła. Nawet EXPLAIN pokazuje to samo. A z "aktywny" zrezygnować nie mogę. To pole określa, czy użytkownik nie zalega z płatnościami, jeśli zalega - produkty nie mogą być wyświetlane.
mkozak
Przy takiej dużej ilości kluczy i kolumn w userach to złączenie zabija ci serv.

Mały hincik i lekka zmiana w zapytaniu i odzyskasz swoje 7 sec.

  1. SELECT p.produkt_id, p.name, p.katalog, p.pod_katalog, p.category_id, o.user_id, o.cena, o.id AS id_offer
  2. FROM produkty p, oferty o
  3. WHERE o.produkt_id = p.produkt_id
  4. AND p.category_id
  5. IN ( 4, 6, 7, 8, 13, 15, 16, 17, 19, 21, 23, 27, 30, 32, 36, 39, 42, 43, 45, 48, 50, 50, 51, 54, 55, 57, 58, 63, 69, 71, 72, 73, 74, 75, 77, 80, 81, 82, 85, 86, 95, 102, 103, 104, 108, 110, 111, 112, 113, 117, 118, 119, 126, 127, 131, 135, 136, 139, 146, 149, 159, 160, 162, 163, 167, 168, 170, 176, 180, 183, 186, 188, 189, 192, 195, 197 )
  6. AND o.cena
  7. BETWEEN 1
  8. AND 99999
  9. AND o.user_id
  10. IN (
  11.  
  12. SELECT u.user_id
  13. FROM uzytkownicy u
  14. USE INDEX ( shop_active )
  15. WHERE u.user_id = o.user_id
  16. AND u.active =1
  17. )
  18. ORDER BY p.name ASC
  19. LIMIT 0 , 20
dr_bonzo
@mkozak: Ciekawe, twoje zapytanie daje:

Kod
id    select_type    table    type    possible_keys    key    key_len    ref    rows    Extra    
1    PRIMARY    p    *ALL*    PRIMARY,category_id    *NULL*    NULL    NULL    15    Using where; Using filesort
1    PRIMARY    o    ref    produkt_id,cena    produkt_id    4    test.p.produkt_id    12    Using where
2    DEPENDENT SUBQUERY    u    ref    shop_active    shop_active    5    const,test.o.user_id    2    Using where; Using index
Walian
Cytat("mkozak")
Przy takiej dużej ilości kluczy i kolumn w userach to złączenie zabija ci serv.

Wiem, bo sprawdzałem różne operacje bez złączeń.

dr_bonzo - nie wiem czy zauważyłeś, ale w jego zapytaniu (EXPLAIN) brak "Using temporary". Może właśnie to dało taki wzrost wydajności?

Zapytanie mkozak-a rzeczywiście działa o 7-8s szybciej (trwa średnio 2 - 2.15 s) i wydaje się zwracać poprawne wyniki, ale musiałem wykorzystać nowy indeks bo pomylił on "active" z "aktywny", ale rozumiem ideę.
Dokładniej sprawdzę wszystko jutro w pracy.
Jeśli ktoś ma jakiś lepszy pomysł, albo jakieś uwagi to proszę śmiało pisać. Nie ukrywam, że 2s to dobry wynik, ale za jakiś czas nie będzie wystarczający ;-)
mkozak
Cytat(Walian @ 17.08.2010, 17:16:31 ) *
Wiem, bo sprawdzałem różne operacje bez złączeń.


Chyba nie do końca napisałem to o co mi chodziło. Miało być "to złączenie z urzytkownicy".

Zrób sobie jakiegoś trigera i pomocnicze pole w tabelce z ofertami, tak żeby wyeliminować to złączenie.
Narazie masz co pokazać szefowi - normalnie premia ci się należy za czterokrotne przyspieszenie działania strony.
Walian
Wprowadziłem nowe zapytanie + nowy indeks i wydaje się działać szybciej.
Na najbardziej zapełnionych drzewkach kategorii czas to ok. 2.5s, na tych mniejszych nawet 0.15s.
No ale wątpię by to był szczyt optymalizacji, także jak ktoś ma jeszcze jakiś pomysł to niech śmiało nawija smile.gif

A czy jeśli wszystkie trzy tabele przerzucę na InnoDB to zapytanie przyspieszy?

Cytat("mkozak")
Zrób sobie jakiegoś trigera i pomocnicze pole w tabelce z ofertami, tak żeby wyeliminować to złączenie.

Chodzi Ci o to, by zmieniać status ofertom, zamiast użytkownikom - jeden czasochłonny zapis, zamiast wielu czasochłonnych odczytów ?
mkozak
To już zależy od tego jak często zmienia się status userów. Jeżeli chodzi o to, że user zostaje "zdeaktywowany" (konto zawieszone) to lepiej w tej sytuacji zmienić statusy ofert na nieaktywne, niż za każdym razem łączyć z userami, bo któryś może ma zawieszone konto.
Walian
Status będzie się zmieniał co miesiąc - lub później - zależy czy ktoś zapłaci wcześniej, czy najpierw poczeka aż go zablokujemy tongue.gif
Tak więc masz rację. Ale to nie będzie koniec optymalizacji bo:
  1. SELECT
  2. SQL_NO_CACHE
  3. SQL_CALC_FOUND_ROWS
  4. `p`.`produkt_id`,
  5. `p`.`name`,
  6. `p`.`katalog`,
  7. `p`.`pod_katalog`,
  8. `p`.`category_id`,
  9. `o`.`user_id`,
  10. `o`.`cena`,
  11. `o`.`id` AS id_offer
  12. FROM
  13. `produkty` `p`,
  14. `oferty` `o`
  15. WHERE
  16. `o`.`produkt_id`=`p`.`produkt_id`
  17. AND
  18. `p`.`category_id` IN (numerki)
  19. ORDER BY
  20. `p`.`name` ASC
  21. LIMIT 0,20

1.60s

  1. SELECT
  2. SQL_NO_CACHE
  3. SQL_CALC_FOUND_ROWS
  4. `p`.`produkt_id`,
  5. `p`.`name`,
  6. `p`.`katalog`,
  7. `p`.`pod_katalog`,
  8. `p`.`category_id`,
  9. `o`.`user_id`,
  10. `o`.`cena`,
  11. `o`.`id` AS id_offer
  12. FROM
  13. `produkty` `p`,
  14. `oferty` `o`
  15. WHERE
  16. `o`.`produkt_id`=`p`.`produkt_id`
  17. AND
  18. `p`.`category_id` IN (numerki)
  19. LIMIT 0,20

0.80s

Jak widać już bez sprawdzania czy "aktywny=1".
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.