MySQL verwendet keine Indizes, wenn eine Verknüpfung mit einer anderen Tabelle hergestellt wird

11

Ich habe zwei Tabellen, die erste Tabelle enthält alle Artikel / Blog-Beiträge innerhalb eines CMS. Einige dieser Artikel erscheinen möglicherweise auch in einer Zeitschrift. In diesem Fall haben sie eine Fremdschlüsselbeziehung zu einer anderen Tabelle, die magazinspezifische Informationen enthält.

Hier ist eine vereinfachte Version der Syntax zum Erstellen von Tabellen für diese beiden Tabellen, wobei einige nicht wesentliche Zeilen entfernt wurden:

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;

Das CMS enthält insgesamt rund 250.000 Artikel. Ich habe ein einfaches Python-Skript geschrieben , mit dem eine Testdatenbank mit Beispieldaten gefüllt werden kann, wenn dieses Problem lokal repliziert werden soll.

Wenn ich aus einer dieser Tabellen auswähle, hat MySQL kein Problem damit, einen geeigneten Index auszuwählen oder Artikel schnell abzurufen. Wenn die beiden Tabellen jedoch in einer einfachen Abfrage zusammengefügt werden, z.

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 kann keine geeigneten Abfragen auswählen und die Leistung sinkt. Hier ist die relevante Erklärung verlängert (die Ausführungszeit beträgt mehr als eine Sekunde):

+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
| 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                     |
+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
  • EDIT SEPT 30: Ich kann die WHEREKlausel aus dieser Abfrage entfernen , aber die EXPLAINsieht immer noch gleich aus und die Abfrage ist immer noch langsam.

Eine mögliche Lösung besteht darin, einen Index zu erzwingen. Das Ausführen derselben Abfrage mit FORCE INDEX (base_articel_date_published)Ergebnissen führt zu einer Abfrage, die in etwa 1,6 Millisekunden ausgeführt wird.

+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+
| 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    |             |
+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+

Ich würde es aus mehreren Gründen vorziehen, keinen Index für diese Abfrage erzwingen zu müssen, wenn ich dies vermeiden kann. Insbesondere kann diese grundlegende Abfrage auf verschiedene Arten gefiltert / geändert werden (z. B. durch Filtern nach issue_slug). Danach ist sie base_article_date_publishedmöglicherweise nicht mehr der beste zu verwendende Index.

Kann jemand eine Strategie zur Verbesserung der Leistung für diese Abfrage vorschlagen?

Joshmaker
quelle
Wenn die Spalte "is_published" nur zwei oder drei Werte enthält, können Sie diesen Index KEY base_article_is_published( is_published) wirklich löschen . Für mich ist es ein boolescher Typ.
Raymond Nijland
bearbeitet die Antwort
Raymond Nijland

Antworten:

5

Was sollte damit geschehen, dass keine "temporäre Verwendung; Dateisortierung verwenden" erforderlich ist, da die Daten bereits in der richtigen Sortierung sind.

Sie müssen den Trick kennen, warum MySQL "Temporär verwenden; Dateisortierung verwenden" benötigt, um diese Notwendigkeit zu beseitigen.

In der zweiten sqlfriddle finden Sie eine Erklärung zum Entfernen der Notwendigkeit

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

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

Funktioniert ziemlich gut, ich brauchte dies auch vor einiger Zeit für Länder- / Stadttabellen. Siehe Demo hier mit Beispieldaten http://sqlfiddle.com/#!2/b34870/41

Bearbeitet möchten Sie diese Antwort möglicherweise auch analysieren, wenn base_article.is_published = 1 immer 1 Datensatz zurückgibt, wie in Ihrer Erklärung erläutert. Eine INNER JOIN-Auslieferungstabelle bietet möglicherweise eine bessere Leistung wie die Abfragen in der folgenden Antwort

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

Raymond Nijland
quelle
Lebensrettende Antwort! Ich habe JOINnur verwendet, aber MySQL hat den Index nicht erfasst. Vielen Dank Raymond
Maximus
4

REFACTOR DIE FRAGE

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;

oder

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;

ÄNDERN SIE IHRE INDEXE

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

VERSUCHE ES !!!

RolandoMySQLDBA
quelle
Refactor: Funktioniert LIMIT 30leider nicht , da sich das in der Unterabfrage befindet (nicht alle dieser 30 Zeilen werden auch in der mag_articlesTabelle enthalten sein). Wenn ich die LIMITin die äußere Abfrage verschiebe, ist die Leistung dieselbe wie in meinem Original. Indizes ändern: MySQL verwendet diesen Index auch nicht. Das Entfernen der WHEREKlausel aus meiner ursprünglichen Abfrage scheint keinen Unterschied zu machen.
Joshmaker
Die zweite Refactor-Methode hat unglaublich gut funktioniert, die Abfragezeit wurde in meiner Tabelle drastisch von 8 Sekunden auf 0,3 Sekunden reduziert ... Danke, Sir!
andreszs