Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Consulta de agregação AVG() muito simples no servidor MySQL leva muito tempo


Para contar o número de linhas com uma data específica, o MySQL precisa localizar esse valor no índice (o que é bem rápido, afinal é para isso que os índices são feitos) e então ler as entradas subsequentes do índice em> até encontrar a próxima data. Dependendo do tipo de dados de esi , isso resultará na leitura de alguns MB de dados para contar suas 700 mil linhas. Ler alguns MB não leva muito tempo (e esses dados podem até já estar armazenados em cache no buffer pool, dependendo da frequência com que você usa o índice).

Para calcular a média de uma coluna que não está incluída no índice, o MySQL irá, novamente, usar o índice para encontrar todas as linhas para aquela data (o mesmo que antes). Mas, além disso, para cada linha encontrada, ele deve ler os dados reais da tabela para essa linha, o que significa usar a chave primária para localizar a linha, ler alguns bytes e repetir isso 700k vezes. Este "acesso aleatório" é muito mais lento do que a leitura sequencial no primeiro caso. (Isso piora com o problema de que "alguns bytes" são o innodb_page_size (16 KB por padrão), então você pode ter que ler até 700k * 16KB =11GB, comparado a "alguns MB" para count(*); e dependendo da configuração de sua memória, alguns desses dados podem não ser armazenados em cache e devem ser lidos do disco.)

Uma solução para isso é incluir todas as colunas usadas no índice (um "índice de cobertura"), por exemplo, crie um índice em date, 01 . Então o MySQL não precisa acessar a tabela em si, e pode continuar, similar ao primeiro método, apenas lendo o índice. O tamanho do índice aumentará um pouco, então o MySQL precisará ler "mais alguns MB" (e executar o avg -operação), mas ainda deve ser uma questão de segundos.

Nos comentários, você mencionou que precisa calcular a média em 24 colunas. Se você deseja calcular a avg para várias colunas ao mesmo tempo, você precisaria de um índice de cobertura em todas elas, por exemplo date, 01, 02, ..., 24 para impedir o acesso à mesa. Esteja ciente de que um índice que contém todas as colunas requer tanto espaço de armazenamento quanto a própria tabela (e levará muito tempo para criar esse índice), portanto, pode depender da importância dessa consulta se vale a pena esses recursos.

Para evitar o limite MySQL de 16 colunas por índice , você pode dividi-lo em dois índices (e duas consultas). Criar, por exemplo os índices date, 01, .., 12 e date, 13, .., 24 , então use
select * from (select `date`, avg(`01`), ..., avg(`12`) 
               from mytable where `date` = ...) as part1
cross join    (select avg(`13`), ..., avg(`24`) 
               from mytable where `date` = ...) as part2;

Certifique-se de documentar isso bem, pois não há razão óbvia para escrever a consulta dessa maneira, mas pode valer a pena.

Se você só fizer a média em uma única coluna, poderá adicionar 24 índices separados (em date, 01 , date, 02 , ...), embora no total, eles exigirão ainda mais espaço, mas podem ser um pouco mais rápidos (já que são menores individualmente). Mas o pool de buffers ainda pode favorecer o índice completo, dependendo de fatores como padrões de uso e configuração de memória, portanto, talvez seja necessário testá-lo.

Desde date faz parte de sua chave primária, você também pode considerar alterar a chave primária para date, esi . Se você encontrar as datas pela chave primária, não precisará de uma etapa adicional para acessar os dados da tabela (pois já acessa a tabela), portanto o comportamento seria semelhante ao índice de cobertura. Mas esta é uma mudança significativa em sua tabela e pode afetar todas as outras consultas (por exemplo, use esi para localizar linhas), por isso deve ser considerado com cuidado.

Como você mencionou, outra opção seria construir uma tabela de resumo onde você armazena valores pré-calculados, especialmente se você não adicionar ou modificar linhas de datas anteriores (ou puder mantê-las atualizadas com um gatilho).