MySQL nie używa indeksów podczas łączenia z inną tabelą

11

Mam dwie tabele, pierwsza tabela zawiera wszystkie artykuły / posty na blogu w systemie CMS. Niektóre z tych artykułów mogą również pojawiać się w czasopiśmie, w którym to przypadku mają związek z kluczem obcym z inną tabelą zawierającą informacje specyficzne dla czasopisma.

Oto uproszczona wersja składni tworzenia tabeli dla tych dwóch tabel z usuniętymi nieistotnymi wierszami:

CREATE TABLE `base_article` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `date_published` datetime DEFAULT NULL,
  `title` varchar(255) NOT NULL,
  `description` text,
  `content` longtext,
  `is_published` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `base_article_date_published` (`date_published`),
  KEY `base_article_is_published` (`is_published`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `mag_article` (
    `basearticle_ptr_id` int(11) NOT NULL,
    `issue_slug` varchar(8) DEFAULT NULL,
    `rubric` varchar(75) DEFAULT NULL,
    PRIMARY KEY (`basearticle_ptr_id`),
    KEY `mag_article_issue_slug` (`issue_slug`),
    CONSTRAINT `basearticle_ptr_id_refs_id` FOREIGN KEY (`basearticle_ptr_id`) REFERENCES `base_article` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CMS zawiera łącznie około 250 000 artykułów, a ja napisałem prosty skrypt w języku Python, którego można użyć do wypełnienia testowej bazy danych przykładowymi danymi, jeśli chcą replikować ten problem lokalnie.

Jeśli wybiorę jedną z tych tabel, MySQL nie będzie miał problemu z wybraniem odpowiedniego indeksu lub szybkim pobieraniem artykułów. Jednak gdy dwie tabele są ze sobą połączone w prostym zapytaniu, takim jak:

SELECT * FROM `base_article` 
INNER JOIN `mag_article` ON (`mag_article`.`basearticle_ptr_id` = `base_article`.`id`)
WHERE is_published = 1
ORDER BY `base_article`.`date_published` DESC
LIMIT 30

MySQL nie wybiera odpowiedniego zapytania i poprawia wydajność. Oto odpowiednie wyjaśnienie wydłużone (czas wykonania wynosi ponad sekundę):

+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
| id | select_type |    table     |  type  |           possible_keys           |   key   | key_len |                  ref                   | rows  | filtered |              Extra              |
+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
|  1 | SIMPLE      | mag_article  | ALL    | PRIMARY                           | NULL    | NULL    | NULL                                   | 23830 | 100.00   | Using temporary; Using filesort |
|  1 | SIMPLE      | base_article | eq_ref | PRIMARY,base_article_is_published | PRIMARY | 4       | my_test.mag_article.basearticle_ptr_id |     1 | 100.00   | Using where                     |
+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
  • EDYCJA WRZESIEŃ 30: Mogę usunąć WHEREklauzulę z tego zapytania, ale EXPLAINnadal wygląda tak samo, a zapytanie jest nadal wolne.

Jednym z potencjalnych rozwiązań jest wymuszenie indeksu. Uruchomienie tego samego zapytania FORCE INDEX (base_articel_date_published)powoduje, że zapytanie wykonuje się w około 1,6 milisekundy.

+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+
| id | select_type |    table     |  type  | possible_keys |             key             | key_len |           ref           | rows | filtered  |    Extra    |
+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+
|  1 | SIMPLE      | base_article | index  | NULL          | base_article_date_published |       9 | NULL                    |   30 | 833396.69 | Using where |
|  1 | SIMPLE      | mag_article  | eq_ref | PRIMARY       | PRIMARY                     |       4 | my_test.base_article.id |    1 | 100.00    |             |
+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+

Wolałbym nie zmuszać indeksu do tego zapytania, jeśli mogę tego uniknąć z kilku powodów. Co najważniejsze, to podstawowe zapytanie może być filtrowane / modyfikowane na różne sposoby (takie jak filtrowanie według issue_slug), po czym base_article_date_publishedmoże nie być najlepszym indeksem do użycia.

Czy ktoś może zasugerować strategię poprawy wydajności dla tego zapytania?

Joshmaker
źródło
jeśli kolumna „is_published” zawiera tylko dwie lub trzy wartości, naprawdę możesz upuścić ten indeks KEY base_article_is_published( is_published) .. wygląda na to, że jest to typ boolowski.
Raymond Nijland
zredagował odpowiedź
Raymond Nijland

Odpowiedzi:

5

Co z tego, powinno to wyeliminować potrzebę „Używania tymczasowego; Używania sortowania plików”, ponieważ dane są już w odpowiednim sortowaniu.

Musisz wiedzieć, dlaczego MySQL potrzebuje „Używanie tymczasowego; Używanie sortowania plików”, aby usunąć tę potrzebę.

Zobacz drugi sqlfriddle, aby uzyskać wyjaśnienie na temat usuwania potrzeby

SELECT
      *
    FROM base_article

    STRAIGHT_JOIN 
      mag_article
    ON
      (mag_article.basearticle_ptr_id = base_article.id)

    WHERE
      base_article.is_published = 1

    ORDER BY
      base_article.date_published DESC

patrz http://sqlfiddle.com/#!2/302710/2

Działa całkiem dobrze, potrzebowałem tego również jakiś czas temu dla tabel kraju / miasta zobacz demo tutaj z przykładowymi danymi http://sqlfiddle.com/#!2/b34870/41

Edytowany możesz również przeanalizować tę odpowiedź, jeśli base_article.is_published = 1 zawsze zwraca 1 rekord, tak jak wyjaśniono wyjaśnienie, tabela dostarczona z WEJŚCIEM WEWNĘTRZNYM może dać lepszą wydajność, podobnie jak zapytania w odpowiedzi poniżej

/programming/18738483/mysql-slow-query-using-filesort/18774937#18774937

Raymond Nijland
źródło
Ratująca życie odpowiedź! Używałem JOINtylko, ale MySQL nie pobierał indeksu. Dzięki bardzo Raymond
Maximus
4

REFAKTOR ZAPYTANIA

SELECT * FROM
(SELECT * FROM base_article
WHERE is_published = 1
ORDER BY date_published LIMIT 30) A
INNER JOIN mag_article B
ON A.id = B.basearticle_ptr_id;

lub

SELECT B.*,C.* FROM
(SELECT id FROM base_article
WHERE is_published = 1
ORDER BY date_published LIMIT 30) A
LEFT JOIN base_article ON A.id = B.id
LEFT JOIN mag_article C ON B.id = C.basearticle_ptr_id;

ZMODYFIKUJ SWOJE INDEKSY

ALTER TABLE base_article DROP INDEX base_article_is_published;
ALTER TABLE base_article ADD INDEX ispub_datepub_index (is_published,date_published);

SPRÓBUJ !!!

RolandoMySQLDBA
źródło
Refaktor: Obawiam się, że nie działa, ponieważ LIMIT 30jest w podzapytaniu (nie wszystkie z tych 30 wierszy również będą w mag_articlestabeli). Jeśli przejdę LIMITdo zewnętrznego zapytania, wydajność będzie taka sama jak w moim oryginale. Modyfikuj indeksy: MySQL również nie używa tego indeksu. Usunięcie WHEREklauzuli z mojego pierwotnego zapytania nie wydaje się mieć znaczenia.
Joshmaker
Druga metoda refaktoryzacji działała niesamowicie dobrze, czas zapytania został znacznie skrócony z 8 do 0,3 sekundy w mojej tabeli ... dziękuję Sir !!
andreszs