2009-04-03 10 views
34

Tengo una tabla con aproximadamente 100.000 publicaciones en el blog, vinculada a una tabla con 50 feeds a través de una relación 1: n. Cuando consulto ambas tablas con una instrucción select, ordenada por un campo de fecha y hora de la tabla de publicaciones, MySQL siempre usa filesort, lo que resulta en tiempos de consulta muy lentos (> 1 segundo). Aquí está el esquema de la tabla postings (simplificado):Optimización del rendimiento MySQL: ordenar por el campo de fecha y hora

+---------------------+--------------+------+-----+---------+----------------+ 
| Field    | Type   | Null | Key | Default | Extra   | 
+---------------------+--------------+------+-----+---------+----------------+ 
| id     | int(11)  | NO | PRI | NULL | auto_increment | 
| feed_id    | int(11)  | NO | MUL | NULL |    | 
| crawl_date   | datetime  | NO |  | NULL |    | 
| is_active   | tinyint(1) | NO | MUL | 0  |    | 
| link    | varchar(255) | NO | MUL | NULL |    | 
| author    | varchar(255) | NO |  | NULL |    | 
| title    | varchar(255) | NO |  | NULL |    | 
| excerpt    | text   | NO |  | NULL |    | 
| long_excerpt  | text   | NO |  | NULL |    | 
| user_offtopic_count | int(11)  | NO | MUL | 0  |    | 
+---------------------+--------------+------+-----+---------+----------------+ 

Y aquí está la tabla feed:

+-------------+--------------+------+-----+---------+----------------+ 
| Field  | Type   | Null | Key | Default | Extra   | 
+-------------+--------------+------+-----+---------+----------------+ 
| id   | int(11)  | NO | PRI | NULL | auto_increment | 
| type  | int(11)  | NO | MUL | 0  |    | 
| title  | varchar(255) | NO |  | NULL |    | 
| website  | varchar(255) | NO |  | NULL |    | 
| url   | varchar(255) | NO |  | NULL |    | 
+-------------+--------------+------+-----+---------+----------------+ 

Y aquí está la consulta que toma> 1 segundo para ejecutar. Tenga en cuenta que el campo post_date tiene un índice, pero MySQL no lo está utilizando para ordenar la tabla de envíos:

SELECT 
    `postings`.`id`, 
    UNIX_TIMESTAMP(postings.post_date) as post_date, 
    `postings`.`link`, 
    `postings`.`title`, 
    `postings`.`author`, 
    `postings`.`excerpt`, 
    `postings`.`long_excerpt`, 
    `feeds`.`title` AS feed_title, 
    `feeds`.`website` AS feed_website 
FROM 
    (`postings`) 
JOIN 
    `feeds` 
ON 
    `feeds`.`id` = `postings`.`feed_id` 
WHERE 
    `feeds`.`type` = 1 AND 
    `postings`.`user_offtopic_count` < 10 AND 
    `postings`.`is_active` = 1 
ORDER BY 
    `postings`.`post_date` desc 
LIMIT 
    15 

El resultado del comando explain extended en esta consulta muestra que MySQL está usando filesort:

+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+ 
| id | select_type | table | type | possible_keys       | key  | key_len | ref      | rows | Extra      | 
+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+ 
| 1 | SIMPLE  | postings | ref | feed_id,is_active,user_offtopic_count | is_active | 1  | const     | 30996 | Using where; Using filesort | 
| 1 | SIMPLE  | feeds | eq_ref | PRIMARY,type       | PRIMARY | 4  | feedian.postings.feed_id |  1 | Using where     | 
+----+-------------+----------+--------+---------------------------------------+-----------+---------+--------------------------+-------+-----------------------------+ 

Cuando elimino la pieza order by, MySQL deja de usar filesort. Indíqueme si tiene alguna idea sobre cómo optimizar esta consulta para que MySQL ordene y seleccione los datos mediante el uso de índices. Ya he intentado algunas cosas, como crear un índice combinado en todos los campos donde/ordenar por, como lo sugieren algunas publicaciones en el blog, pero tampoco funcionó.

+7

Me gusta mucho la forma clara y detallada en que lo ha pedido. – tpdi

Respuesta

34

Cree un índice compuesto ya sea en postings (is_active, post_date) (en ese orden).

Se usará tanto para filtrar en is_active como para ordenar por post_date.

MySQL debe mostrar el método de acceso REF sobre este índice en EXPLAIN EXTENDED.

Tenga en cuenta que usted tiene una condición de filtrado RANGE sobre user_offtopic_count, es por eso que no se puede usar un índice sobre este campo, tanto en el filtrado y en la clasificación por otro campo.

Dependiendo de qué tan selectiva es su user_offtopic_count (i. E. El número de filas satisfacen user_offtopic_count < 10), puede ser más útil para crear un índice en user_offtopic_count y dejar que se ordenarán los post_dates.

Para hacer esto, cree un índice compuesto en postings (is_active, user_offtopic_count) y asegúrese de que se utiliza el método de acceso RANGE sobre este índice.

Qué índice será más rápido depende de su distribución de datos. Crear ambos índices, FORCE ellos y ver cuál es más rápido:

CREATE INDEX ix_active_offtopic ON postings (is_active, user_offtopic_count); 
CREATE INDEX ix_active_date ON postings (is_active, post_date); 

SELECT 
    `postings`.`id`, 
    UNIX_TIMESTAMP(postings.post_date) as post_date, 
    `postings`.`link`, 
    `postings`.`title`, 
    `postings`.`author`, 
    `postings`.`excerpt`, 
    `postings`.`long_excerpt`, 
    `feeds`.`title` AS feed_title, 
    `feeds`.`website` AS feed_website 
FROM 
    `postings` FORCE INDEX (ix_active_offtopic) 
JOIN 
    `feeds` 
ON 
    `feeds`.`id` = `postings`.`feed_id` 
WHERE 
    `feeds`.`type` = 1 AND 
    `postings`.`user_offtopic_count` < 10 AND 
    `postings`.`is_active` = 1 
ORDER BY 
    `postings`.`post_date` desc 
LIMIT 
    15 

/* This should show RANGE access with few rows and keep the FILESORT */ 

SELECT 
    `postings`.`id`, 
    UNIX_TIMESTAMP(postings.post_date) as post_date, 
    `postings`.`link`, 
    `postings`.`title`, 
    `postings`.`author`, 
    `postings`.`excerpt`, 
    `postings`.`long_excerpt`, 
    `feeds`.`title` AS feed_title, 
    `feeds`.`website` AS feed_website 
FROM 
    `postings` FORCE INDEX (ix_active_date) 
JOIN 
    `feeds` 
ON 
    `feeds`.`id` = `postings`.`feed_id` 
WHERE 
    `feeds`.`type` = 1 AND 
    `postings`.`user_offtopic_count` < 10 AND 
    `postings`.`is_active` = 1 
ORDER BY 
    `postings`.`post_date` desc 
LIMIT 
    15 

/* This should show REF access with lots of rows and no FILESORT */ 
+0

¡Eso me gustó, muchas gracias! Tuve que usar el índice de fuerza para obtener el mejor índice utilizado. Ahora estamos usando múltiples índices combinados para las diferentes consultas. –

3

MySQL tiene dos algoritmos de archivos: un filesort más antiguo que ordena los registros en el disco, y una nueva versión que funciona en la memoria.

Si no puede usar un índice en la primera tabla de la unión para ordenar la consulta, tendrá que hacer una clasificación de archivos. Si el conjunto de resultados antes de la clasificación convertida a formato de ancho fijo es mayor que el de ordenación O si contiene campos de texto, deberá usar el algoritmo de archivo más lento en disco (la segunda condición se cumple dado que su consulta tiene un texto campo).

MySQL está eligiendo usar la columna is_active, aparentemente porque cree que la columna es más selectiva al eliminar filas antes de continuar con las otras uniones y en las condiciones. Lo primero que sugeriría sería intentar crear índices compuestos con post_date, feed_id y las columnas en la condición where, p. (is_active, user_offtopic_count, post_date, feed_id).

+0

¡Gracias por la explicación! –

3

Además, es importante recordar que MySQL no usará un índice si la columna que está ordenando por tiene una función que se le aplica.

También debería probar aliasing postings.post_date como algo más. Esto le indicará a MySQL que ordene por la columna inalterada, y usted todavía seleccionará la marca de tiempo de unix.

Cuestiones relacionadas