Tengo una consulta que está tardando mucho en ejecutarse (más de 15 segundos) y solo empeora con el tiempo a medida que crece mi conjunto de datos. Lo he optimizado en el pasado, y he agregado índices, clasificación a nivel de código y otras optimizaciones, pero necesita un poco más de refinamiento.
SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM `sounds`
INNER JOIN ratings ON sounds.id = ratings.rateable_id
WHERE (ratings.rateable_type = 'Sound'
AND sounds.blacklisted = false
AND sounds.ready_for_deployment = true
AND sounds.deployed = true
AND sounds.type = "Sound"
AND sounds.created_at > "2011-03-26 21:25:49")
GROUP BY ratings.rateable_id
El propósito de la consulta es obtener sound id
la calificación promedio de los sonidos publicados más recientes. Hay alrededor de 1500 sonidos y 2 millones de clasificaciones.
Tengo varios índices sobre sounds
mysql> show index from sounds;
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+————+
| sounds | 0 | PRIMARY | 1 | id | A | 1388 | NULL | NULL | | BTREE | |
| sounds | 1 | sounds_ready_for_deployment_and_deployed | 1 | deployed | A | 5 | NULL | NULL | YES | BTREE | |
| sounds | 1 | sounds_ready_for_deployment_and_deployed | 2 | ready_for_deployment | A | 12 | NULL | NULL | YES | BTREE | |
| sounds | 1 | sounds_name | 1 | name | A | 1388 | NULL | NULL | | BTREE | |
| sounds | 1 | sounds_description | 1 | description | A | 1388 | 128 | NULL | YES | BTREE | |
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+
y varios en ratings
mysql> show index from ratings;
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+————+
| ratings | 0 | PRIMARY | 1 | id | A | 2008251 | NULL | NULL | | BTREE | |
| ratings | 1 | index_ratings_on_rateable_id_and_rating | 1 | rateable_id | A | 18 | NULL | NULL | | BTREE | |
| ratings | 1 | index_ratings_on_rateable_id_and_rating | 2 | rating | A | 9297 | NULL | NULL | YES | BTREE | |
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
Aquí está el EXPLAIN
mysql> EXPLAIN SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM sounds INNER JOIN ratings ON sounds.id = ratings.rateable_id WHERE (ratings.rateable_type = 'Sound' AND sounds.blacklisted = false AND sounds.ready_for_deployment = true AND sounds.deployed = true AND sounds.type = "Sound" AND sounds.created_at > "2011-03-26 21:25:49") GROUP BY ratings.rateable_id;
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+——————+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+——————+
| 1 | SIMPLE | ratings | index | index_ratings_on_rateable_id_and_rating | index_ratings_on_rateable_id_and_rating | 9 | NULL | 2008306 | Using where |
| 1 | SIMPLE | sounds | eq_ref | PRIMARY,sounds_ready_for_deployment_and_deployed | PRIMARY | 4 | redacted_production.ratings.rateable_id | 1 | Using where |
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+-------------+
Guardo en caché los resultados una vez obtenidos, por lo que el rendimiento del sitio no es un gran problema, pero mis calentadores de caché tardan más y más en ejecutarse debido a que esta llamada tarda tanto, y eso está comenzando a convertirse en un problema. Esto no parece una gran cantidad de números para resolver en una consulta ...
¿Qué más puedo hacer para que esto funcione mejor ?
fuente
EXPLAIN
salida?EXPLAIN SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM sounds INNER JOIN ratings ON sounds.id = ratings.rateable_id WHERE (ratings.rateable_type = 'Sound' AND sounds.blacklisted = false AND sounds.ready_for_deployment = true AND sounds.deployed = true AND sounds.type = "Sound" AND sounds.created_at > "2011-03-26 21:25:49") GROUP BY ratings.rateable_id
Respuestas:
Después de revisar la consulta, las tablas y las cláusulas WHERE AND GROUP BY, recomiendo lo siguiente:
Recomendación # 1) Refactorizar la consulta
Reorganicé la consulta para hacer tres (3) cosas:
Aquí está mi consulta propuesta:
Recomendación # 2) Indice la tabla de sonidos con un índice que acomode la cláusula WHERE
Las columnas de este índice incluyen todas las columnas de la cláusula WHERE con valores estáticos primero y el objetivo móvil en último lugar
Sinceramente creo que se sorprenderá gratamente. Darle una oportunidad !!!
ACTUALIZACIÓN 2011-05-21 19:04
Acabo de ver la cardinalidad. ¡Ay! Cardinalidad de 1 para rateable_id. Chico, me siento estúpido !!!
ACTUALIZACIÓN 2011-05-21 19:20
Quizás hacer el índice sea suficiente para mejorar las cosas.
ACTUALIZACIÓN 2011-05-21 22:56
Por favor ejecute esto:
ACTUALIZACIÓN 2011-05-21 23:34
Lo refactoré nuevamente. Prueba este por favor:
ACTUALIZACIÓN 2011-05-21 23:55
Lo refactoré nuevamente. Pruebe este por favor (la última vez):
ACTUALIZACIÓN 2011-05-22 00:12
¡Odio rendirme!
ACTUALIZACIÓN 2011-05-22 07:51
Me ha estado molestando que las calificaciones vuelvan con 2 millones de filas en el EXPLICAR. Entonces, me golpeó. Es posible que necesite otro índice en la tabla de calificaciones que comience con rateable_type:
El objetivo de este índice es reducir la tabla temporal que manipula las clasificaciones para que sea inferior a 2 millones. Si podemos obtener esa tabla temporal significativamente más pequeña (al menos la mitad), entonces podemos tener una mejor esperanza en su consulta y la mía trabajando más rápido también.
Después de hacer ese índice, vuelva a intentar mi consulta propuesta original y también pruebe la suya:
ACTUALIZACIÓN 2011-05-22 18:39: PALABRAS FINALES
Refactoré una consulta en un procedimiento almacenado y agregué un índice para ayudar a responder una pregunta sobre cómo acelerar las cosas. Recibí 6 votos a favor, la respuesta fue aceptada y obtuve una recompensa de 200.
También había refactorizado otra consulta (resultados marginales) y agregué un índice (resultados dramáticos). Recibí 2 votos a favor y la respuesta fue aceptada.
Agregué un índice para otro desafío de consulta y fui votado una vez
y ahora su pregunta .
El deseo de responder a todas las preguntas como estas (incluida la suya) se inspiró en un video de YouTube que vi en consultas de refactorización.
Gracias de nuevo, @coneybeare !!! Quería responder a esta pregunta en la mayor medida posible, no solo aceptar puntos o elogios. ¡Ahora, puedo sentir que gané los puntos!
fuente
Gracias por la salida EXPLICAR. Como se puede deducir de esa declaración, la razón por la que está tardando tanto es el escaneo completo de tablas en la tabla de calificaciones. Nada en la instrucción WHERE está filtrando las 2 millones de filas.
Podría agregar un índice en ratings.type, pero supongo que la CARDINALIDAD será muy baja y seguirá escaneando bastantes filas
ratings
.Alternativamente, puede intentar usar pistas de índice para obligar a mysql a usar los índices de sonidos.
Actualizado:
Si fuera yo, agregaría un índice,
sounds.created
ya que tiene la mejor oportunidad de filtrar las filas y probablemente obligará al optimizador de consultas mysql a usar los índices de la tabla de sonidos. Solo tenga cuidado con las consultas que usan marcos de tiempo creados durante mucho tiempo (1 año, 3 meses, solo depende del tamaño de la tabla de sonidos).fuente
Si tiene que ser una consulta disponible "sobre la marcha" , eso limita un poco sus opciones.
Voy a sugerir dividir y vencer por este problema.
fuente
sounds
,ratings
a la consulta del medio), pero bloqueó mi casilla sql y tuve que matar el proceso.Use JOINs, no subconsultas. ¿Alguno de sus intentos de subconsulta ayudó?
MOSTRAR CREAR TABLA sonidos \ G
MOSTRAR CREAR TABLA ratings \ G
A menudo es beneficioso tener índices "compuestos", no índices de una sola columna. Quizás INDEX (tipo, created_at)
Está filtrando en ambas tablas en un JOIN; es probable que sea un problema de rendimiento.
Le recomendamos que tenga una identificación de aumento automático
ratings
, cree una tabla de resumen y use la identificación de AI para realizar un seguimiento de dónde la "dejó". Sin embargo, no almacene promedios en una tabla de resumen:En cambio, mantenga la SUMA (rating.rating). El promedio de promedios es matemáticamente incorrecto para calcular un promedio; (suma de sumas) / (suma de conteos) es correcta.
fuente