Optimización del rendimiento de MySQL: ordenar por campo datetime


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

+---------------------+--------------+------+-----+---------+----------------+
| 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 tarda >1 segundo en ejecutarse. Tenga en cuenta que el campo post_date tiene un índice, pero MySQL no lo está usando para ordenar la tabla de publicaciones:

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 parte order by, MySQL deja de usar filesort. Por favor, hágamelo saber si tiene alguna idea sobre cómo optimizar esta consulta para obtener MySQL para ordenar y seleccionar los datos mediante el uso de índices. Ya he intentado algunas cosas, como crear un índice combinado en todos los campos where/order by, como sugirieron algunos publicaciones de blog, pero esto tampoco funcionó.

Author: Dennis G., 2009-04-03

3 answers

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

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

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

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

Dependiendo de cuán selectivo sea su user_offtopic_count (es decir, cuántas filas satisfacen user_offtopic_count < 10), puede ser más útil para crear un índice en user_offtopic_count y dejar que los post_dates se ordenen.

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. Cree ambos índices, FORCE y vea 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 */
 36
Author: Quassnoi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-03 18:13:33

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

Si no puede usar un índice en la primera tabla de la combinación para ordenar la consulta, tendrá que hacer un filesort. Si resultset antes de ordenar convertido a formato de ancho fijo es mayor que el búfer de ordenación O si contiene cualquier campo de texto, tendrá que usar el algoritmo más lento en el disco filesort (la segunda condición se cumple ya que su consulta tiene un campo de texto).

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

 3
Author: ʞɔıu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-03 17:59:04

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

También debe intentar aliasing publicaciones.post_date como otra cosa. Esto le dirá a MySQL que ordene por la columna inalterada, y aún así seleccionará la marca de tiempo unix.

 3
Author: Chris Henry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-25 05:41:53