it-swarm.com.de

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

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, und 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 WHERE -Klausel aus dieser Abfrage entfernen, aber die EXPLAIN sieht 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) 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 issue_slug Filtern), wonach base_article_date_published Möglicherweise nicht mehr der beste zu verwendende Index ist.

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

11
Joshmaker

Was ist damit, dass dies die Notwendigkeit eines "Temporären Verwenden; Verwenden von Dateisortierung" beseitigen sollte, 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.

Eine Erklärung zum Entfernen der Notwendigkeit finden Sie im zweiten sqlfriddle .

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 Sie möchten 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

https://stackoverflow.com/questions/18738483/mysql-slow-query-using-filesort/18774937#18774937

5
Raymond Nijland

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

4
RolandoMySQLDBA