Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Optymalizacja zapytania + indeksy
Forum PHP.pl > Forum > Bazy danych > MySQL
Walian
Mam takie zapytanie:
  1. SELECT
  2. SQL_CALC_FOUND_ROWS
  3. a.article_id,
  4. a.article_status,
  5. a.article_views,
  6. a.article_date_added,
  7. a.article_date_modified,
  8. a.article_desc,
  9. a.article_adder,
  10. a.article_last_changer,
  11. a.article_name,
  12. (
  13. SELECT
  14. m.member_level
  15. FROM
  16. members AS `m`
  17. WHERE
  18. LOWER(m.member_username)=LOWER(a.article_adder)
  19. ) AS `adder_level`,
  20. (
  21. SELECT
  22. m.member_level
  23. FROM
  24. members AS `m`
  25. WHERE
  26. LOWER(m.member_username)=LOWER(a.article_last_changer)
  27. ) AS `last_changer_level`
  28. FROM
  29. articles AS `a`
  30. WHERE
  31. a.article_category_id=1 -- tu odpowiednie Id
  32. ORDER BY
  33. a.article_date_added DESC
  34. LIMIT 0,20;


Służy do wylistowania / wyszukania artykułów dla danej kategorii.
Dodatkowo zwraca mi dodatkowe informacje o dodającym oraz o osobie, która jako ostatnia edytowała artykuł (edytować może autor lub administrator), m.in to czy dodający lub ostatni edytujący jest administratorem, czy też zwykłym użytkownikiem. Oczywiście zamiast tego zera przy limicie pojawia się odpowiedni offset winksmiley.jpg

Dla testu dodałem ok. 1000 artykułów do bazy.
Niestety zapytanie zbyt długo się wykonuje, a i tak testuję to tylko na localhoście.
Czas więc zoptymalizować składnię zapytania o ile się da no i dodać indeksy.

Tabela artykułów:
  1. CREATE TABLE `articles` (
  2. `article_id` mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT,
  3. `article_category_id` tinyint(3) UNSIGNED NOT NULL DEFAULT 0,
  4. `article_status` tinyint(3) UNSIGNED NOT NULL DEFAULT 0,
  5. `article_views` int(11) UNSIGNED NOT NULL DEFAULT 0,
  6. `article_date_added` datetime NOT NULL,
  7. `article_date_modified` datetime NOT NULL,
  8. `article_desc` tinytext COLLATE utf8_polish_ci NOT NULL DEFAULT '',
  9. `article_content` mediumtext COLLATE utf8_polish_ci NOT NULL DEFAULT '',
  10. `article_adder` varchar(12) COLLATE utf8_polish_ci NOT NULL DEFAULT '',
  11. `article_last_changer` varchar(12) COLLATE utf8_polish_ci NOT NULL DEFAULT '',
  12. `article_name` varchar(64) COLLATE utf8_polish_ci NOT NULL DEFAULT '',
  13. `article_safe_name` varchar(64) COLLATE utf8_polish_ci NOT NULL DEFAULT '',
  14. PRIMARY KEY (`article_id`),
  15. UNIQUE KEY `article_name` (`article_name`),
  16. UNIQUE KEY `article_safe_name` (`article_safe_name`)
  17. ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci;

Pole member_username w tabeli members:
  1. `member_username` varchar(12) COLLATE utf8_polish_ci NOT NULL DEFAULT '',


I pytanie tak na marginesie - stosować słówko AS w zapytaniach czy lepiej skrócony zapis bez tego słowa kluczowego?
gothye
skoro wyszukujesz po
  1. article_category_id
to czemu tutaj klucza nie zastosowałeś ? 

Mchl
Podzapytania w liście pól wykonują się dla każdego wiersza tabeli 'articles'. Pora zapoznać się ze złączeniami.

Kod
<pre>   SELECT
       SQL_CALC_FOUND_ROWS
       a.article_id,
       a.article_status,
       a.article_views,
       a.article_date_added,
       a.article_date_modified,
       a.article_desc,
       a.article_adder,
       a.article_last_changer,
       a.article_name,
       ad.member_level as `adder_level`,
       alc.member.level as `last_changer_level`
       FROM
          articles as `a`
       INNER JOIN
         members AS ad
       ON
         LOWER(ad.member_username)=LOWER(a.article_adder)
       INNER JOIN
         members AS alc
       ON
         LOWER(alc.member_username)=LOWER(a.article_last_changer)
       WHERE
          a.article_category_id=1 -- tu odpowiednie Id
       ORDER BY
          a.article_date_added DESC
      LIMIT 0,20;
Walian
Ok dzięki Mchl.
Złączenia już wykorzystywałem kilka razy, jednak w tym wypadku nie udawało mi się uzyskać pożądanych efektów więc tymczasowo zastosowałem podzapytania.
Co prawda musiałem zmienić w Twoim zapytaniu INNER JOIN-y na LEFT JOIN-y bo nie dostawałem wyników, ale teraz już jest OK.
Tak w ogóle to masz literówkę w:
Cytat
alc.member.level


Cytat("gothye")
skoro wyszukujesz po
  1. article_category_id

to czemu tutaj klucza nie zastosowałeś ?

Bo kategorii będzie bardzo niewiele, na początku ok. 15, maksymalnie myślę, że około 30.

Mam jeszcze jeden mały problem.
W menu mam wylistowane kategorie i chcę by obok każdej w nawiasie pokazało ile przypada widocznych artykułów dla danej kategorii.
O tym czy artykuł jest widoczny decyduje jego status z zakresu od 0 do 6.
Widoczne są tylko te ze statusem od 0 do 2 lub ze statusem 6.
Kategorie też mają swoje statusy, ale tylko od 0 do 3 i widoczne są wszystkie ze statusem mniejszym niż 3.

Tabela kategorii:
  1. CREATE TABLE `articles_categories` (
  2. `category_id` tinyint(3) UNSIGNED NOT NULL AUTO_INCREMENT,
  3. `category_sort_order` tinyint(3) UNSIGNED NOT NULL DEFAULT 0,
  4. `category_status` tinyint(3) UNSIGNED NOT NULL DEFAULT 0,
  5. `category_visible_articles` mediumint(8) UNSIGNED NOT NULL DEFAULT 0,
  6. `category_date_added` datetime NOT NULL,
  7. `category_date_modified` datetime NOT NULL,
  8. `category_adder` varchar(12) COLLATE utf8_polish_ci NOT NULL DEFAULT '',
  9. `category_last_changer` varchar(12) COLLATE utf8_polish_ci NOT NULL DEFAULT '',
  10. `category_name` varchar(32) COLLATE utf8_polish_ci NOT NULL DEFAULT '',
  11. `category_safe_name` varchar(32) COLLATE utf8_polish_ci NOT NULL DEFAULT '',
  12. `category_title` varchar(255) COLLATE utf8_polish_ci NOT NULL DEFAULT '',
  13. PRIMARY KEY (`category_id`),
  14. UNIQUE KEY `category_name` (`category_name`),
  15. UNIQUE KEY `category_safe_name` (`category_safe_name`),
  16. UNIQUE KEY `category_title` (`category_title`)
  17. ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci;


Na razie najlepsze zapytanie jakie udało mi się wymyślić ma taką postać:
  1. SELECT
  2. ac.category_id,
  3. ac.category_status,
  4. ac.category_name,
  5. ac.category_title,
  6. COUNT(a.article_id) AS `articles_count`
  7. FROM
  8. articles_categories AS `ac`
  9. LEFT JOIN
  10. articles AS `a`
  11. ON
  12. (a.article_category_id=ac.category_id) AND ((a.article_status < 3) OR (a.article_status = 6))
  13. WHERE
  14. (ac.category_status < 3)
  15. GROUP BY
  16. ac.category_id;


Dla ok. 1000 artykułów zapytanie wykonuje się średnio w 0.45 s, czyli chyba za długo.
Indeksów chyba nie ma sensu zakładać na pola id i status prawda?

Jak sami widzicie zapytanie mocno kombinowane :/
Zapytanie musi mi zwrócić również kategorie nieposiadające artykułów (0 w nawiasie).
Mchl
Cytat(Walian @ 20.02.2010, 22:27:40 ) *
Bo kategorii będzie bardzo niewiele, na początku ok. 15, maksymalnie myślę, że około 30.


No to co? Ważne po ile będzie artykułów w każdej kategorii.

Spróbuj tak:

Kod
SELECT
   ac.category_id,
   ac.category_status,
   ac.category_name,
   ac.category_title,
   a.articles_count
FROM
   articles_categories AS `ac`
LEFT JOIN (
   SELECT
      article_category_id
      COUNT(*) AS articles_count
   FROM
      articles AS `a`
   WHERE
      (a.article_status < 3) OR (a.article_status = 6)
   GROUP BY
      article_category_id
) AS a
ON
   (a.article_category_id=ac.category_id)
WHERE
   (ac.category_status < 3)
Walian
0.13 s smile.gif
Dzięki wielkie.
Mchl
Możesz spróbować jeszcze z indeksem na:
ALTER TABLE articles_categories ADD INDEX category_id_category_status(category_id,category_status)
albo odwrotnie... nigdy nie jestem pewien w którą stronę MySQL sobie ułoży kolumny tongue.gif
Walian
Mam jeszcze jeden problem, to zapytanie które służy do pobrania artykułów danego użytkownika ("Moje artykuły"):
  1. EXPLAIN
  2. SELECT
  3. SQL_CALC_FOUND_ROWS
  4. a.article_id,
  5. a.article_status,
  6. a.article_views,
  7. a.article_date_added,
  8. a.article_date_modified,
  9. a.article_desc,
  10. a.article_adder,
  11. a.article_last_changer,
  12. a.article_name,
  13. alc.member_level AS `last_changer_level`
  14. FROM
  15. articles AS `a`
  16. LEFT JOIN
  17. members AS `alc`
  18. ON
  19. alc.member_username=a.article_last_changer
  20. WHERE
  21. a.article_adder='wojtas'
  22. ORDER BY
  23. a.article_date_added DESC
  24. LIMIT 0,20;


Wynik:


Tabela ma łącznie 2247 artykułów czyli o 2 więcej niż ma testowy użytkownik.
Zapytanie bez explain wykonuje się w ok. 0.36 s.
Jak widać explain zwrócił "rows" o wartości 2245 - czy tak powinno być skoro ja wybieram tylko 20 artykułów? Zapytanie jest dobrze ułożone?
Mchl
SQL_CALC_FOUND_ROWS wymusza wykonanie zapytania bez LIMIT (w uproszczeniu), po to aby policzyć właśnie wszystkie wiersze spełniające dany warunek.
Walian
A bez uproszczenia biggrin.gif ?

1. Wykonanie zapytań z EXPLAIN zarówno z SQL_CALC_FOUND_ROWS jak i bez niego - zwraca ten sam wynik.
2. Wykonanie obu zapytań bez EXPLAIN skutkuje tym, że to bez SQL_CALC_FOUND_ROWS jest prawie dwukrotnie szybsze.
Mchl
Generalnie zaufał bymw tym wypadku ludziom z Percony:
http://www.mysqlperformanceblog.com/2007/0...alc_found_rows/
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.