2010-06-10 8 views
8

estoy tratando de optimizar esta consulta:MySQL Query lenta: INNER JOIN + ORDER BY causas filesort

 
SELECT `posts`.* FROM `posts` INNER JOIN `posts_tags` 
    ON `posts`.id = `posts_tags`.post_id 
    WHERE (((`posts_tags`.tag_id = 1))) 
    ORDER BY posts.created_at DESC; 

El tamaño de las tablas es 38k filas, y 31k y MySQL utiliza "filesort", por lo que se pone bastante lento. Traté de usar índices diferentes, sin suerte.

 
CREATE TABLE `posts` (
    `id` int(11) NOT NULL auto_increment, 
    `created_at` datetime default NULL, 
    PRIMARY KEY (`id`), 
    KEY `index_posts_on_created_at` (`created_at`), 
    KEY `for_tags` (`trashed`,`published`,`clan_private`,`created_at`) 
) ENGINE=InnoDB AUTO_INCREMENT=44390 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 

CREATE TABLE `posts_tags` (
    `id` int(11) NOT NULL auto_increment, 
    `post_id` int(11) default NULL, 
    `tag_id` int(11) default NULL, 
    `created_at` datetime default NULL, 
    `updated_at` datetime default NULL, 
    PRIMARY KEY (`id`), 
    KEY `index_posts_tags_on_post_id_and_tag_id` (`post_id`,`tag_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=63175 DEFAULT CHARSET=utf8 
 
+----+-------------+------------+--------+--------------------------+--------------------------+---------+---------------------+-------+-----------------------------------------------------------+ 
| id | select_type | table  | type | possible_keys   | key      | key_len | ref     | rows | Extra              | 
+----+-------------+------------+--------+--------------------------+--------------------------+---------+---------------------+-------+-----------------------------------------------------------+ 
| 1 | SIMPLE  | posts_tags | index | index_post_id_and_tag_id | index_post_id_and_tag_id | 10  | NULL    | 24159 | Using where; Using index; Using temporary; Using filesort | 
| 1 | SIMPLE  | posts  | eq_ref | PRIMARY     | PRIMARY     | 4  | .posts_tags.post_id |  1 |               | 
+----+-------------+------------+--------+--------------------------+--------------------------+---------+---------------------+-------+-----------------------------------------------------------+ 
2 rows in set (0.00 sec) 

¿Qué tipo de índice que necesito definir para evitar el uso de MySQL filesort? ¿Es posible cuando el campo de orden no está en la cláusula where?

actualización: Perfilado resultados:

 
mysql> show profile for query 1; 
+--------------------------------+----------+ 
| Status       | Duration | 
+--------------------------------+----------+ 
| starting      | 0.000027 | 
| checking query cache for query | 0.037953 | 
| Opening tables     | 0.000028 | 
| System lock     | 0.010382 | 
| Table lock      | 0.023894 | 
| init       | 0.000057 | 
| optimizing      | 0.010030 | 
| statistics      | 0.000026 | 
| preparing      | 0.000018 | 
| Creating tmp table    | 0.128619 | 
| executing      | 0.000008 | 
| Copying to tmp table   | 1.819463 | 
| Sorting result     | 0.001092 | 
| Sending data     | 0.004239 | 
| end       | 0.000012 | 
| removing tmp table    | 0.000885 | 
| end       | 0.000006 | 
| end       | 0.000005 | 
| query end      | 0.000006 | 
| storing result in query cache | 0.000005 | 
| freeing items     | 0.000021 | 
| closing tables     | 0.000013 | 
| logging slow query    | 0.000004 | 
| cleaning up     | 0.000006 | 
+--------------------------------+----------+ 

update2:

consulta real (algunos campos más booleanos, índices más inútiles)

SELECT `posts`.* FROM `posts` INNER JOIN `posts_tags` 
    ON `posts`.id = `posts_tags`.post_id 
    WHERE ((`posts_tags`.tag_id = 7971)) 
     AND (((posts.trashed = 0) 
     AND (`posts`.`published` = 1 
     AND `posts`.`clan_private` = 0)) 
     AND ((`posts_tags`.tag_id = 7971))) 
    ORDER BY created_at DESC LIMIT 0, 10;

Empty set (1.25 sec)

Con fuera ORDEN POR - 0.01s.

 

+----+-------------+------------+--------+-----------------------------------------+-----------------------+---------+---------------------+-------+--------------------------+ 
| id | select_type | table  | type | possible_keys       | key     | key_len | ref     | rows | Extra     | 
+----+-------------+------------+--------+-----------------------------------------+-----------------------+---------+---------------------+-------+--------------------------+ 
| 1 | SIMPLE  | posts_tags | index | index_posts_tags_on_post_id_and_tag_id | index_posts_tags_... | 10  | NULL    | 23988 | Using where; Using index | 
| 1 | SIMPLE  | posts  | eq_ref | PRIMARY,index_posts_on_trashed_and_crea | PRIMARY    | 4  | .posts_tags.post_id |  1 | Using where    | 
+----+-------------+------------+--------+-----------------------------------------+-----------------------+---------+---------------------+-------+--------------------------+ 

SOLUCIÓN

  1. consulta actualizada a "ORDER BY posts_tags.created_at DESC" (dos pequeños cambios en el código de aplicación)
  2. Índice añadido: index_posts_tags_on_created_at.

¡Eso es todo!

Respuesta

3

Usted tendría que desnormalizar un poco, y copiar el campo posts.created_at en la tabla post_tags (lo llamé post_created_at, usted podría nombrar como quieras):

CREATE TABLE `posts_tags` (
    `id` int(11) NOT NULL auto_increment, 
    `post_id` int(11) default NULL, 
    `tag_id` int(11) default NULL, 
    `post_created_at` datetime default NULL, 
    `created_at` datetime default NULL, 
    `updated_at` datetime default NULL, 
    PRIMARY KEY (`id`), 
    KEY `index_posts_tags_on_post_id_and_tag_id` (`post_id`,`tag_id`) 
) ENGINE=InnoDB; 

y luego añadir un índice a posts_tags en

(tag_id, post_created_at) 

que permitirá a la consulta para obtener todos los mensajes de una etiqueta, en el orden correcto, sin filesort.

+0

Gracias! He actualizado consulta para utilizar posts_tags.created_at para la clasificación (sin necesidad de conocer la etiqueta la fecha de creación) y se añadió index_posts_tags_on_created_at ... no más filesort! :) – Alexander

+0

¿Es posible informar al planificador de consultas que dos columnas en tablas diferentes son iguales, por lo que no tendría que especificar explícitamente que debería usar posts_tags.created_at en lugar de posts.created_at? – sorenbs

0

su clave index_posts_on_created_at está clasificado de forma ascendente, pero desea que los resultados ordenados descendente

+0

Utiliza filesort con ambas ASC y DESC ordenar pedidos :( – Alexander

1

intente cambiar CLAVE index_posts_tags_on_post_id_and_tag_id (post_id, tag_id) para introducir index_posts_tags_tag_id (tag_id) y Repost Explica.

¿Cuál es la distribución de TagID con Posts_Tags?