Pergunta

Como acelerar select count(*) com group by
É muito lento e é usado com muita freqüência.
Eu tenho um grande problema usando select count(*) e group by com uma tabela com mais de 3.000.000 de linhas.

select object_title,count(*) as hot_num   
from  relations 
where relation_title='XXXX'   
group by object_title  

relation_title , object_title é varchar. onde relation_title = 'XXXX' , que retorna mais de 1.000.000 linhas, chumbo para os índices em object_title não poderia funcionar bem.

Foi útil?

Solução

Aqui estão algumas coisas que eu tentaria, em ordem crescente de dificuldade:

(mais fácil) - Certifique-se de que você tem o índice de cobertura direita

CREATE INDEX ix_temp ON relations (relation_title, object_title);

Isso deve maximizar perf dado o seu esquema existente, uma vez que (a menos que sua versão do otimizador do MySQL é! Realmente estúpido) que irá minimizar a quantidade de I / Os necessário para satisfazer sua consulta (ao contrário, se o índice está na ordem inversa em que todo o índice deve ser digitalizados) e vai cobrir a consulta para que você não terá que tocar o índice agrupado.

(um pouco mais difícil) - certifique-se seus campos varchar são tão pequenos quanto possível

Um dos desafios perf com índices varchar no MySQL é que, ao processar uma consulta, o tamanho declarado completo do campo será puxado para a RAM. Então, se você tem um varchar (256), mas está usando apenas 4 caracteres, você ainda está pagando o uso de RAM de 256 bytes enquanto a consulta está sendo processada. Ouch! Então, se você pode reduzir seus limites varchar facilmente, isso deve acelerar suas consultas.

(mais difícil) - Normalize

30% de suas linhas com um único valor da cadeia é um grito claro para normalizar em outra tabela para que você não está duplicando cordas milhões de vezes. Considere normalizando em três mesas e usando inteiro IDs para se juntar a eles.

Em alguns casos, você pode normalizar debaixo das cobertas e esconder a normalização com vistas que correspondem ao nome da tabela atual ... então você só precisa fazer a sua insert / update / consultas de DELETE ciente da normalização, mas pode deixar seus SELECTs sozinho.

(mais difícil) - Hash suas colunas de cordas e indexar os hashes

Se os meios normalizando mudando muito código, mas você pode mudar seu esquema um pouco, você pode querer considerar a criação de hashes de 128 bits para as suas colunas de cordas (usando o MD5 função ). Neste caso (ao contrário de normalização), você não tem que mudar todas as suas dúvidas, apenas as inserções e alguns dos SELECTs. De qualquer forma, você vai querer botar seus campos de cadeia, e em seguida, criar um índice sobre os hashes, por exemplo,

CREATE INDEX ix_temp ON relations (relation_title_hash, object_title_hash);

Note que você vai precisar de brincar com o SELECT para certificar-se de que você está fazendo o cálculo através do índice hash e não puxar para o índice agrupado (necessário para resolver o valor de texto real de object_title, a fim de satisfazer a consulta ).

Além disso, se relation_title tem um pequeno tamanho varchar mas título objeto tem um longo tamanho, então você pode potencialmente hash somente object_title e criar o índice em (relation_title, object_title_hash).

Note que esta solução só ajuda se um ou ambos destes campos é muito longa em relação ao tamanho dos hashes.

Além disso, note que existem interessantes impactos de maiúsculas e minúsculas / agrupamento de hashing, já que o hash de uma string em minúsculas não é o mesmo como um hash de uma uma maiúscula. Então você vai precisar para se certificar de que você aplicar canonização às cordas antes de hashing eles-- em outras palavras, apenas a hash de minúsculas se você estiver em um DB case-insensitive. Você também pode querer cortar espaços desde o início ou final, dependendo de como seus punhos do DB líder / espaços à direita.

Outras dicas

A indexação as colunas na cláusula GROUP BY seria a primeira coisa a tentar, usando um índice composto. Uma consulta como esta pode, potencialmente, ser respondidas utilizando apenas os dados do índice, evitando a necessidade de examinar a tabela em tudo. Uma vez que os registros no índice são classificadas, o DBMS não deve precisar para realizar uma espécie separada como parte do processamento de grupo. No entanto, o índice vai abrandar atualizações para a mesa, para ser cauteloso com isso, se a sua mesa experimenta atualizações pesados.

Se você usar InnoDB para o armazenamento de tabelas, linhas da tabela serão fisicamente agrupados pelo índice de chave primária. Se isso (ou uma parte levando dela) passa a corresponder ao seu GROUP BY chave, que deve acelerar uma consulta como esta porque os registros relacionados serão recuperados juntos. Mais uma vez, isso evita ter de realizar uma espécie separada.

Em geral, os índices de bitmap seria outra alternativa eficaz, mas o MySQL não suporta atualmente estes, tanto quanto eu sei.

A visão materializada seria outra abordagem possível, mas novamente este não é suportado diretamente no MySQL. No entanto, se você não exigem as estatísticas COUNT para ser completamente up-to-date, você pode periodicamente executar uma instrução CREATE TABLE ... AS SELECT ... para armazenar em cache manualmente os resultados. Isto é um pouco feio, pois não é transparente, mas pode ser aceitável no seu caso.

Você também pode manter uma tabela de cache de nível lógico usando disparadores. Esta tabela teria uma coluna para cada coluna em sua cláusula GROUP BY, com uma coluna de contagem para armazenar o número de linhas para esse valor chave de agrupamento particular. Toda vez que uma linha é adicionado ou atualizado na tabela de base, inserir ou aumentar / diminuir a linha contador na tabela de resumo para essa chave de agrupamento particular. Isto pode ser melhor do que o falso materializou abordagem vista, como o resumo em cache será sempre up-to-date, e cada atualização é feita de forma incremental e deve ter menos de um impacto de recursos. Eu acho que você teria que tomar cuidado para contenção de bloqueio na tabela de cache, no entanto.

Se você tem InnoDB, COUNT (*) e qualquer outra função agregada vai fazer uma varredura na tabela. Eu vejo algumas soluções aqui:

  1. Use gatilhos e armazenar agregados em uma tabela separada. Prós: integridade. Contras: atualizações lentas
  2. Use o processamento de filas. Prós: atualizações rápidas. Contras:. Velho estado pode persistir até que a fila é processado para que o usuário pode sentir falta de integridade
  3. totalmente separar a camada de acesso de armazenamento e armazenar agregados em uma tabela separada. A camada de armazenamento estará ciente da estrutura de dados e pode aplicar deltas em vez de fazer as contagens completas. Por exemplo, se você fornecer uma funcionalidade "addObject" dentro que você vai saber quando um objeto foi adicionado e, assim, o agregado seria afetada. Então você faz única uma update table set count = count + 1. Prós: atualizações rápidas, a integridade (você pode querer usar um bloqueio embora no caso de vários clientes podem alterar o mesmo registro). Contras:. Você acopla um pouco de lógica de negócios e armazenamento

Eu vejo que algumas pessoas têm perguntado o motor que você estava usando para a consulta. Eu recomendo que você usar MyISAM para as seguintes reasions:

InnoDB - @Sorin Mocanu devidamente identificados que você vai fazer uma mesa varredura completa, independentemente de índices.

MyISAM - sempre mantém a linha atual contar calhar.

Por fim, como @justin afirmou, certifique-se que o índice de cobertura adequada:

CREATE INDEX ix_temp ON relations (relation_title, object_title);

teste count (myprimaryindexcolumn) e comparar o desempenho de seu count (*)

há um ponto em que você realmente precisa mais RAM / CPUs / IO. Você pode ter sucesso que o seu hardware.

Vou tomar nota que, geralmente, não é eficaz para usar índices (a menos que sejam cobertura) para consultas que atingiram mais de 1-2% dos total de linhas em uma tabela. Se o seu grande consulta está fazendo índice de procura e pesquisas de marcadores, poderia ser por causa de um plano em cache que foi de apenas uma consulta do dia-total. tente adicionar em COM (INDEX = 0) para forçar uma varredura da tabela e ver se ele é mais rápido.

tirar isso de: http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.sqlserver.programming&tid=4631bab4-0104- 47aa-b548-e8428073b6e6 & cat = & lang = & cr = & sloc = & p = 1

Se você qual é o tamanho da tabela inteira, você deve consultar as tabelas meta ou esquema info (que existem em cada DBMS Eu sei, mas eu não tenho certeza sobre o MySQL). Se a consulta é seletiva, você tem que ter certeza há um índice para ele.

AFAIK não há nada mais que você pode fazer.

Gostaria de sugerir para arquivar dados a menos que haja qualquer motivo específico para mantê-lo no banco de dados ou você pode particionar os dados e executar consultas separadamente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top