MySQL no usa índices cuando se une contra otra tabla

11

Tengo dos tablas, la primera tabla contiene todos los artículos / publicaciones de blog dentro de un CMS. Algunos de estos artículos también pueden aparecer en una revista, en cuyo caso tienen una relación de clave externa con otra tabla que contiene información específica de la revista.

Aquí hay una versión simplificada de la sintaxis de creación de tablas para estas dos tablas con algunas filas no esenciales eliminadas:

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;

El CMS contiene alrededor de 250,000 artículos en total y he escrito un script simple de Python que se puede usar para llenar una base de datos de prueba con datos de muestra si quieren replicar este problema localmente.

Si selecciono una de estas tablas, MySQL no tiene problemas para elegir un índice apropiado o recuperar artículos rápidamente. Sin embargo, cuando las dos tablas se unen en una consulta simple como:

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 no puede elegir una consulta adecuada y el rendimiento cae en picado. Aquí está la explicación relevante extendida (el tiempo de ejecución para el cual es más de un segundo):

+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
| 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                     |
+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
  • EDITAR EL 30 DE SEPTIEMBRE: puedo eliminar la WHEREcláusula de esta consulta, pero EXPLAINtodavía se ve igual y la consulta sigue siendo lenta.

Una posible solución es forzar un índice. Ejecutar la misma consulta con FORCE INDEX (base_articel_date_published)resultados en una consulta que se ejecuta en alrededor de 1.6 milisegundos.

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

Preferiría no tener que forzar un índice en esta consulta si puedo evitarlo, por varias razones. En particular, esta consulta básica se puede filtrar / modificar de varias maneras (como el filtrado por el issue_slug) después de lo cual base_article_date_publishedpuede que ya no sea el mejor índice para usar.

¿Alguien puede sugerir una estrategia para mejorar el rendimiento de esta consulta?

Joshmaker
fuente
si la columna "is_published" solo mantiene dos o tres valores, realmente podría eliminar ese índice KEY base_article_is_published( is_published) ... me parece que es un tipo booleano ...
Raymond Nijland
editó la respuesta
Raymond Nijland el

Respuestas:

5

¿Qué pasa con esto? Esto debería eliminar la necesidad de un "Uso temporal; Uso de clasificación de archivos" porque los datos ya están en el orden correcto.

Necesita saber el truco por el que MySQL necesita "Usar temporalmente; Usar ordenar archivos" para eliminar esa necesidad.

Consulte el segundo sqlfriddle para obtener una explicación sobre cómo eliminar la necesidad

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

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

Funciona bastante bien, necesitaba esto también hace algún tiempo para ver las tablas de país / ciudad, vea la demostración aquí con datos de ejemplo http://sqlfiddle.com/#!2/b34870/41

Editado, también es posible que desee analizar esta respuesta si base_article.is_published = 1 siempre devuelve 1 registro como su explicación explicada, una tabla entregada INNER JOIN puede proporcionar un mejor rendimiento como las consultas en la respuesta a continuación

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

Raymond Nijland
fuente
¡Respuesta que salva vidas! Estaba usando JOINsolo pero MySQL no estaba recogiendo el índice. Muchas gracias Raymond
Maximus
4

REFACTOR LA CONSULTA

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;

MODIFICAR SUS ÍNDICES

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

DARLE UNA OPORTUNIDAD !!!

RolandoMySQLDBA
fuente
Refactor: no funciona, me temo, porque LIMIT 30está en la subconsulta (no todas esas 30 filas también estarán en la mag_articlestabla). Si muevo LIMITa la consulta externa, el rendimiento es el mismo que en mi original. Modificar índices: MySQL tampoco usa ese índice. Eliminar la WHEREcláusula de mi consulta original no parece hacer la diferencia.
Joshmaker
El segundo método de refactorización funcionó increíblemente bien, el tiempo de consulta se ha reducido drásticamente de 8 segundos a 0.3 segundos en mi tabla ... ¡gracias señor!
andreszs