MySQL non utilizza gli indici quando si unisce su un'altra tabella


11

Ho due tabelle, la prima tabella contiene tutti gli articoli / post di blog in un CMS. Alcuni di questi articoli possono anche apparire in una rivista, nel qual caso hanno una relazione di chiave esterna con un'altra tabella che contiene informazioni specifiche della rivista.

Ecco una versione semplificata della sintassi della tabella di creazione per queste due tabelle con alcune righe non essenziali eliminate:

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;

Il CMS contiene circa 250.000 articoli in totale e ho scritto un semplice script Python che può essere utilizzato per popolare un database di test con dati di esempio se vogliono replicare questo problema localmente.

Se seleziono da una di queste tabelle, MySQL non ha problemi a scegliere un indice appropriato o a recuperare rapidamente gli articoli. Tuttavia, quando le due tabelle vengono unite insieme in una semplice query come:

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 non riesce a scegliere una query appropriata e il crollo delle prestazioni. Ecco la spiegazione pertinente estesa (il tempo di esecuzione è superiore a un secondo):

+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
| 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                     |
+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
  • MODIFICA 30 SETTEMBRE: posso rimuovere la WHEREclausola da questa query, ma EXPLAINsembra sempre la stessa e la query è ancora lenta.

Una potenziale soluzione è forzare un indice. L'esecuzione della stessa query con FORCE INDEX (base_articel_date_published)risultati in una query che viene eseguita in circa 1,6 millisecondi.

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

Preferirei non dover forzare un indice su questa query se posso evitarlo, per diversi motivi. In particolare, questa query di base può essere filtrata / modificata in vari modi (come il filtro per issue_slug), dopodiché base_article_date_publishedpotrebbe non essere più il miglior indice da utilizzare.

Qualcuno può suggerire una strategia per migliorare le prestazioni di questa query?


se la colonna "is_published" contiene solo due o tre valori potresti davvero eliminare quell'indice KEY base_article_is_published( is_published) .. mi sembra che sia un tipo booleano ..
Raymond Nijland,

ha modificato la risposta
Raymond Nijland,

Risposte:


5

Che ne dici di questo dovrebbe eliminare la necessità di un "Utilizzo temporaneo; Utilizzo di filesort" perché i dati sono già nel giusto ordinamento.

È necessario conoscere il trucco per cui MySQL necessita di "Utilizzo temporaneo; Utilizzo di filesort" per rimuovere tale necessità.

Vedi il secondo sqlfriddle per una spiegazione sulla rimozione della necessità

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

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

Funziona abbastanza bene, ne avevo bisogno anche qualche tempo fa per le tabelle Paese / città, vedi la demo qui con i dati di esempio http://sqlfiddle.com/#!2/b34870/41

Modificato, potresti anche voler analizzare questa risposta se base_article.is_published = 1 restituisce sempre 1 record come spiegato, una tabella di consegna INNER JOIN può fornire prestazioni migliori come le query nella risposta di seguito

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


Risposta salvavita! Stavo usando JOINsolo ma MySQL non stava rilevando l'indice. Grazie mille Raymond
Massimo

4

RIFORMA IL QUERY

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;

o

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;

MODIFICA I TUOI INDICI

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

PROVACI !!!


Refactor: non funziona, temo, perché LIMIT 30è nella sottoquery (non tutte quelle 30 righe saranno anche nella mag_articlestabella). Se sposto il LIMITnella query esterna, le prestazioni sono le stesse del mio originale. Modifica indici: MySQL non usa neanche quell'indice. Rimuovere la WHEREclausola dalla mia query originale non sembra fare la differenza.
Joshmaker,

Il secondo metodo refactor ha funzionato incredibilmente bene, il tempo di interrogazione è stato ridotto drasticamente da 8 secondi a 0,3 secondi nella mia tabella ... grazie signore !!
andreszs
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.