Eu tenho uma consulta que está demorando muito tempo para ser executada (mais de 15 segundos) e só piora com o tempo à medida que meu conjunto de dados cresce. Eu otimizei isso no passado e adicionei índices, classificação em nível de código e outras otimizações, mas ele precisa de mais refinamentos.
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
O objetivo da consulta é obter sound id
a classificação média e dos sons lançados mais recentes. Existem cerca de 1500 sons e 2 milhões de classificações.
Eu tenho vários índices em 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 | |
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+
e vários em 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 | |
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
Aqui está o 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 |
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+-------------+
Como os resultados são obtidos em cache, o desempenho do site não é muito problemático, mas meus aquecedores de cache estão demorando mais e mais para serem executados devido a essa chamada levar tanto tempo e isso está começando a se tornar um problema. Isso não parece muitos números para triturar em uma consulta…
O que mais posso fazer para melhorar esse desempenho ?
fonte
EXPLAIN
saída?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
Respostas:
Depois de examinar a consulta, as tabelas e as cláusulas WHERE AND GROUP BY, recomendo o seguinte:
Recomendação nº 1) Refatorar a consulta
Reorganizei a consulta para fazer três (3) coisas:
Aqui está minha consulta proposta:
Recomendação nº 2) Indexe a tabela de sons com um índice que acomodará a cláusula WHERE
As colunas desse índice incluem todas as colunas da cláusula WHERE com valores estáticos primeiro e o destino móvel por último
Eu sinceramente acredito que você ficará agradavelmente surpreendido. De uma chance !!!
UPDATE 2011-05-21 19:04
Acabei de ver a cardinalidade. OUCH !!! Cardinalidade de 1 para rateable_id. Cara, eu me sinto idiota !!!
UPDATE 2011-05-21 19:20
Talvez fazer o índice seja suficiente para melhorar as coisas.
UPDATE 2011-05-21 22:56
Por favor, execute isto:
UPDATE 2011-05-21 23:34
Eu refatorei novamente. Tente este por favor:
UPDATE 2011-05-21 23:55
Eu refatorei novamente. Tente este por favor (última vez):
UPDATE 2011-05-22 00:12
Eu odeio desistir !!!!
UPDATE 2011-05-22 07:51
Está me incomodando que as classificações voltem com 2 milhões de linhas no EXPLAIN. Então, isso me atingiu. Você pode precisar de outro índice na tabela de classificações, que começa com rateable_type:
O objetivo deste índice é reduzir a tabela temporária que manipula classificações para que seja menor que 2 milhões. Se conseguirmos reduzir significativamente essa tabela temporária (pelo menos metade), teremos uma esperança melhor em sua consulta e a minha também trabalhará mais rapidamente.
Depois de criar esse índice, tente novamente minha consulta proposta original e tente a sua:
ATUALIZAÇÃO 22-05-2011 18:39: PALAVRAS FINAIS
Eu refatorava uma consulta em um procedimento armazenado e adicionava um índice para ajudar a responder a uma pergunta sobre como acelerar as coisas. Recebi 6 votos positivos, a resposta foi aceita e recebi uma recompensa de 200.
Eu também refatorei outra consulta (resultados marginais) e adicionei um índice (resultados dramáticos). Recebi 2 votos positivos e a resposta foi aceita.
Adicionei um índice para mais um desafio de consulta e fui votado uma vez
e agora sua pergunta .
O desejo de responder a todas as perguntas como essas (incluindo a sua) foi inspirado em um vídeo do YouTube que eu assisti nas consultas de refatoração.
Mais uma vez obrigado, @coneybeare !!! Eu queria responder a essa pergunta o máximo possível, não apenas aceitar pontos ou elogios. Agora, sinto que ganhei os pontos !!!
fonte
Obrigado pela saída EXPLAIN. Como você pode perceber a partir dessa afirmação, a razão pela qual está demorando tanto é a tabela completa na tabela de classificações. Nada na instrução WHERE está filtrando as 2 milhões de linhas.
Você pode adicionar um índice em ratings.type, mas meu palpite é que a CARDINALIDADE será muito baixa e você ainda estará pesquisando algumas linhas
ratings
.Como alternativa, você pode tentar usar dicas de índice para forçar o mysql a usar os índices de sons.
Atualizada:
Se fosse eu, adicionaria um índice,
sounds.created
pois ele tem a melhor chance de filtrar as linhas e provavelmente forçará o otimizador de consultas do mysql a usar os índices da tabela de sons. Cuidado com as consultas que usam prazos criados há muito tempo (1 ano, 3 meses, depende apenas do tamanho da tabela de sons).fonte
Se essa tiver que ser uma consulta disponível "on-the-fly" , isso limitará um pouco suas opções.
Vou sugerir dividir e conquistar para esse problema.
fonte
sounds
,ratings
à consulta do meio), mas trancado minha caixa de sql e eu tive que matar o processo.Use JOINs, não subconsultas. Alguma de sua tentativa de subconsulta ajudou?
MOSTRAR CRIAR TABELA sons \ G
MOSTRAR CREATE TABLE ratings \ G
Muitas vezes, é benéfico ter índices "compostos", não os de coluna única. Talvez INDEX (tipo, created_at)
Você está filtrando nas duas tabelas em um JOIN; isso provavelmente será um problema de desempenho.
Recomenda que você tenha um ID de incremento automático
ratings
, crie uma tabela de resumo e use o ID do AI para acompanhar onde você "parou". No entanto, não armazene médias em uma tabela de resumo:Em vez disso, mantenha o SUM (ratings.rating). A média das médias é matematicamente incorreta para calcular uma média; (soma de somas) / (soma de contagens) está correto.
fonte