MySQL n'utilise pas d'index lors de la jointure avec une autre table


11

J'ai deux tableaux, le premier tableau contient tous les articles / articles de blog dans un CMS. Certains de ces articles peuvent également apparaître dans un magazine, auquel cas ils ont une relation de clé étrangère avec une autre table contenant des informations spécifiques au magazine.

Voici une version simplifiée de la syntaxe de création de table pour ces deux tables avec quelques lignes non essentielles supprimées:

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;

Le CMS contient environ 250 000 articles au total et j'ai écrit un script Python simple qui peut être utilisé pour remplir une base de données de test avec des exemples de données s'ils souhaitent reproduire ce problème localement.

Si je sélectionne dans l'une de ces tables, MySQL n'a aucun problème à choisir un index approprié ou à récupérer des articles rapidement. Cependant, lorsque les deux tables sont réunies dans une requête simple telle que:

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 ne parvient pas à choisir une requête appropriée et les chutes de performances. Voici l'explication pertinente étendue (dont le temps d'exécution est supérieur à une seconde):

+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
| 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: je peux supprimer la WHEREclause de cette requête, mais le EXPLAINreste toujours le même et la requête est toujours lente.

Une solution potentielle consiste à forcer un index. L'exécution de la même requête avec des FORCE INDEX (base_articel_date_published)résultats dans une requête qui s'exécute dans environ 1,6 millisecondes.

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

Je préférerais ne pas avoir à forcer un index sur cette requête si je peux l'éviter, pour plusieurs raisons. Plus particulièrement, cette requête de base peut être filtrée / modifiée de diverses manières (comme le filtrage par le issue_slug), après quoi base_article_date_publishedil ne peut plus être le meilleur index à utiliser.

Quelqu'un peut-il suggérer une stratégie pour améliorer les performances de cette requête?


si la colonne "is_published" ne conserve que deux ou trois valeurs, vous pourriez vraiment supprimer cet index KEY base_article_is_published( is_published) .. me semble que c'est un type booléen ..
Raymond Nijland

édité la réponse
Raymond Nijland

Réponses:


5

Qu'en est-il de cela cela devrait supprimer la nécessité d'un "Utilisation temporaire; Utilisation du tri de fichiers" parce que les données sont déjà dans le bon tri.

Vous devez savoir pourquoi MySQL a besoin de "Utiliser temporaire; Utiliser le tri de fichiers" pour supprimer ce besoin.

Voir deuxième sqlfriddle pour une explication sur la suppression du besoin

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

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

Fonctionne assez bien, j'en avais besoin il y a aussi quelque temps pour les tableaux de pays / villes voir la démo ici avec des exemples de données http://sqlfiddle.com/#!2/b34870/41

Modifié, vous pouvez également vouloir analyser cette réponse si base_article.is_published = 1 renvoie toujours 1 enregistrement comme votre expliqué, une table de livraison INNER JOIN peut donner de meilleures performances comme les requêtes dans la réponse ci-dessous

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


Réponse salvatrice! J'utilisais JOINseulement mais MySQL ne récupérait pas d'index. Merci beaucoup Raymond
Maximus

4

RÉFLÉCHIR LA REQUÊTE

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;

ou

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;

MODIFIEZ VOS INDICES

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

ESSAIE !!!


Refactor: ne fonctionne pas, je le crains, car le LIMIT 30est dans la sous-requête (toutes ces 30 lignes ne seront pas également dans le mag_articlestableau). Si je déplace le LIMITvers la requête externe, les performances sont les mêmes que dans mon original. Modifier les index: MySQL n'utilise pas non plus cet index. La suppression de la WHEREclause de ma requête d'origine ne semble pas faire de différence.
Joshmaker

La deuxième méthode de refactorisation a incroyablement bien fonctionné, le temps de requête a été considérablement réduit de 8 secondes à 0,3 seconde dans ma table ... merci monsieur !!
andreszs
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.