Agrupar por hora em um grande conjunto de dados

12

Usando o MS SQL 2008, estou selecionando um campo médio de 2,5 milhões de registros. Cada registro representa um segundo. MyField é uma média horária desses registros de 1 segundo. É claro que a CPU do servidor atinge 100% e a seleção leva muito tempo. Possivelmente, preciso salvar esses valores médios para que o SQL não precise selecionar todos os registros em cada solicitação. O que pode ser feito?

  SELECT DISTINCT
         CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR)
ORDER BY TimeStamp

fonte
6
O TimeStamp faz parte de um índice em cluster? Deve ser ...
@antisanity - por quê? ele é estourar o limite de CPU não io disco
Jack diz tentar topanswers.xyz

Respostas:

5

A parte da consulta que maximiza a CPU por longos períodos são as funções da cláusula GROUP BY e o fato de que o agrupamento sempre exigirá uma classificação não indexada nessa instância. Embora um índice no campo de carimbo de data e hora ajude o filtro inicial, esta operação deve ser executada em todas as linhas correspondentes ao filtro. Acelerar isso está usando uma rota mais eficiente para fazer o mesmo trabalho sugerido por Alex, mas você ainda tem uma enorme ineficiência, porque qualquer combinação de funções que você usa no planejador de consultas não poderá criar algo que será ajudado por qualquer índice e, portanto, ele precisará percorrer todas as linhas executando primeiro as funções para calcular os valores de agrupamento; somente então ele poderá solicitar os dados e calcular os agregados nos agrupamentos resultantes.

Portanto, a solução é, de alguma forma, tornar o grupo de processos algo que ele possa usar um índice ou remover a necessidade de considerar todas as linhas correspondentes de uma só vez.

Você pode manter uma coluna extra para cada linha que contém o tempo arredondado para a hora e indexar essa coluna para uso em tais consultas. Isso está desnormalizando seus dados para que possa parecer "sujo", mas funcionaria e seria mais limpo do que armazenar em cache todos os agregados para uso futuro (e atualizar esse cache à medida que os dados base forem alterados). A coluna extra deve ser mantida por gatilho ou ser uma coluna computada persistente, em vez de ser mantida pela lógica em outro lugar, pois isso garantirá todos os locais atuais e futuros que podem inserir dados ou atualizar as colunas de carimbo de data / hora ou as linhas existentes resultam em dados consistentes no novo coluna. Você ainda pode obter o MIN (carimbo de data / hora). O que a consulta resultará dessa maneira ainda é um passeio por todas as linhas (isso não pode ser evitado, obviamente), mas é possível indexar por ordem, saída de uma linha para cada agrupamento à medida que chega ao próximo valor no índice, em vez de precisar lembrar o conjunto inteiro de linhas para uma operação de classificação não indexada antes que o agrupamento / agregação possa ser executado. Também usará muito menos memória, pois não será necessário lembrar de nenhuma linha de valores de agrupamento anteriores para processar a que está sendo visualizada agora ou o restante.

Esse método elimina a necessidade de encontrar algum lugar na memória para todo o conjunto de resultados e faz a classificação não indexada para a operação do grupo e remove o cálculo dos valores do grupo da grande consulta (movendo esse trabalho para os INSERTs / UPDATEs individuais que produzem o dados) e deve permitir que essas consultas sejam executadas de maneira aceitável sem a necessidade de manter um armazenamento separado dos resultados agregados.

Um método que nãodesnormalizar seus dados, mas ainda exigir estrutura extra, é usar um "horário", neste caso um contendo uma linha por hora durante todo o tempo que você provavelmente considerar. Essa tabela não consumiria uma quantidade significativa de espaço em um banco de dados ou em um tamanho apreciável - para cobrir um período de tempo de 100 anos, uma tabela contendo uma linha de duas datas (o início e o fim da hora, como '2011-01-01 @ 00: 00: 00.0000 ',' 2011-01-01 @ 00: 00: 59.9997 ', o "9997" é o menor número de milissegundos que um campo DATETIME não arredondará para o próximo segundo), que fazem parte do a chave primária em cluster ocupará ~ 14Mbyte de espaço (8 + 8 bytes por linha * 24 horas / dia * 365,25 dias / ano * 100, mais um pouco para a sobrecarga da estrutura em árvore do índice em cluster, mas essa sobrecarga não será significativa) .

SELECT CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour
     , MIN([timestamp]) as TimeStamp
     , AVG(MyField) As AvgField
FROM TimeRangeByHours tt
INNER JOIN MyData md ON md.TimeStamp BETWEEN tt.StartTime AND tt.EndTime
WHERE tt.StartTime > '4/10/2011'
GROUP BY tt.StartTime
ORDER BY tt.StartTime

Isso significa que o planejador de consultas pode organizar o uso do índice em MyData.TimeStamp. O planejador de consultas deve ser brilhante o suficiente para descobrir que pode percorrer a tabela mansa na etapa com o índice em MyData.TimeStamp, produzindo novamente uma linha por agrupamento e descartando cada conjunto ou linhas à medida que atinge o próximo valor de agrupamento. Não é possível armazenar todas as linhas intermediárias em algum lugar da RAM e executar uma classificação não indexada nelas. É claro que esse método exige que você crie o cronograma e verifique se ele abrange o suficiente para trás e para frente, mas você pode usá-lo para consultas em vários campos de datas em consultas diferentes, onde a opção "coluna extra" exigiria uma coluna computada extra para cada campo de data necessário para filtrar / agrupar dessa maneira e o tamanho pequeno da tabela (a menos que você precise abranger 10,

O método do cronograma possui uma diferença extra (que pode ser bastante vantajosa) em comparação com a situação atual e a solução da coluna computada: ele pode retornar linhas por períodos para os quais não há dados, simplesmente alterando INNER JOIN na consulta de exemplo acima ser ESQUERDO EXTERIOR.

Algumas pessoas sugerem não ter uma tabela de horários física, mas sempre retornando-a de uma função de retorno de tabela. Isso significa que o conteúdo da tabela de horários nunca é armazenado no disco (ou precisa ser lido a partir) e se a função for bem escrita, você nunca precisará se preocupar com o tempo que a tabela de tempo precisa para ir e voltar no tempo, mas eu duvide do custo da CPU de produzir uma tabela na memória para algumas linhas. Toda consulta vale a pequena economia de aborrecimento ao criar (e manter, caso seu período de tempo precise estender além do limite da sua versão inicial) o horário físico.

Uma observação: você também não precisa da cláusula DISTINCT na sua consulta original. O agrupamento garantirá que essas consultas retornem apenas uma linha por período considerado, para que o DISTINCT não faça nada além de girar um pouco mais a CPU (a menos que o planejador de consultas perceba que o distinto seria um não operacional, caso em que ignore-o e não use tempo de CPU extra).

David Spillett
fonte
3

Veja esta pergunta ( data mínima) Além disso, por que se preocupar em converter tudo em string - você pode fazer isso mais tarde (se precisar).

  SELECT DISTINCT
         dateadd(hour,datediff(hour,0,[timestamp]),0) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY dateadd(hour,datediff(hour,0,[timestamp],0);
ORDER BY TimeStamp
Hogan
fonte
1

Deseja tornar a consulta mais rápida ou está perguntando como criar um instantâneo de dados e salvá-lo?

Se você deseja torná-lo mais rápido, definitivamente precisa de um índice no campo TimeStamp. Além disso, eu sugiro usar isso para converter em hora:

select convert(varchar(13), getdate(), 121)

Se você precisar fazer um instantâneo e reutilizá-lo posteriormente, use insert intopara criar uma nova tabela com os resultados da sua consulta. Tabela de índice de acordo e use-a. Pelo que entendi, você precisará de um índice no TimeStampHour.

Além disso, você pode configurar um trabalho que agrega dados diários em sua nova tabela agregada.

Alex Aza
fonte
-1

Ao converter seu grupo por cláusula em uma string como essa, você está essencialmente tornando-o um hit não indexado em todas as linhas do banco de dados. É isso que está matando seu desempenho. Qualquer servidor decente poderá lidar com um agregado simples como esse em um milhão de registros, se os índices forem usados ​​corretamente. Eu modificaria sua consulta e colocaria um índice agrupado em seus registros de data e hora. Isso resolverá o seu problema de desempenho, enquanto o cálculo dos dados a cada hora está apenas adiando o problema.


fonte
1
-1 - não você não está "tornando-se um hit não indexados para cada linha no banco de dados" - nenhum índice para TimeStampainda será usada para filtrar as linhas
Jack diz tentativa topanswers.xyz
-3

Eu consideraria abandonar a idéia de implementar esse tipo de cálculo usando um modelo de banco de dados relacional. Especialmente se você tiver muitos pontos de dados para os quais você coleta valores a cada segundo.

Se você tiver o dinheiro, considere comprar um historiador de dados de processo dedicado, como:

  1. Honeywell Uniformance PHD
  2. Osisoft PI
  3. Aspentech IP21
  4. etc.

Esses produtos podem armazenar grandes quantidades de dados incrivelmente densos de séries temporais (em formatos proprietários), permitindo simultaneamente o processamento rápido de consultas de extração de dados. As consultas podem especificar muitos pontos de dados (também chamados de tags), longos intervalos de tempo (meses / anos) e, além disso, podem fazer uma ampla variedade de cálculos de dados resumidos (incluindo médias).

.. e em uma observação geral: eu sempre tento evitar o uso da DISTINCTpalavra - chave ao escrever SQL. Quase nunca é uma boa ideia. No seu caso, você poderá descartar DISTINCTe obter os mesmos resultados adicionando MIN([timestamp])à sua GROUP BYcláusula.


fonte
1
Isso não é realmente preciso. Um banco de dados relacional é perfeitamente adequado para 2,5 milhões de registros. E ele nem está fazendo junções em várias mesas. A primeira indicação de que você precisa desnormalizar seus dados ou migrar para um sistema não relacional é quando você está fazendo junções grandes e complexas em várias tabelas. O conjunto de dados do pôster realmente soa como um uso perfeitamente aceitável de um sistema de banco de dados relacional.