Agrupar por hora en un gran conjunto de datos

12

Usando MS SQL 2008, estoy seleccionando un campo promedio de 2.5 millones de registros. Cada registro representa un segundo. MyField es un promedio por hora de esos registros de 1 segundo. Por supuesto, la CPU del servidor alcanza el 100% y la selección lleva demasiado tiempo. Posiblemente necesito guardar esos valores promediados para que SQL no tenga que seleccionar todos esos registros en cada solicitud. ¿Qué se puede hacer?

  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

fuente
66
¿Es TimeStamp parte de un índice agrupado? Debería ser ...
@antisanity: ¿por qué? él está maximizando la CPU, no el disco io
Jack dice que intente topanswers.xyz

Respuestas:

5

La parte de la consulta es maximizar la CPU durante largos períodos son las funciones en la cláusula GROUP BY y el hecho de que la agrupación siempre requerirá una ordenación no indexada en esta instancia. Si bien un índice en el campo de marca de tiempo ayudará al filtro inicial, esta operación debe realizarse en cada fila que coincida con el filtro. Acelerar esto es usar una ruta más eficiente para hacer el mismo trabajo sugerido por Alex ayudará, pero aún tiene una gran ineficiencia allí porque cualquier combinación de funciones que use el planificador de consultas no podrá llegar a algo que será ayudado por cualquier índice, por lo que tendrá que ejecutar cada fila ejecutando primero las funciones para calcular los valores de agrupación, solo entonces puede ordenar los datos y calcular los agregados sobre las agrupaciones resultantes.

Entonces, la solución es de alguna manera hacer que el proceso se agrupe por algo para lo que pueda usar un índice, o de lo contrario eliminar la necesidad de considerar todas las filas coincidentes a la vez.

Puede mantener una columna adicional para cada fila que contenga el tiempo redondeado a la hora e indexar esta columna para usarla en dichas consultas. Esto está denormalizando sus datos, por lo que podría sentirse "sucio", pero funcionaría y sería más limpio que almacenar en caché todos los agregados para su uso futuro (y actualizar esa caché a medida que se modifican los datos base). La columna adicional debe mantenerse por disparador o ser una columna calculada persistente, en lugar de mantenerse por lógica en otro lugar, ya que esto garantizará que todos los lugares actuales y futuros que puedan insertar datos o actualizar las columnas de marca de tiempo o las filas existentes den como resultado datos consistentes en el nuevo columna. Aún puede obtener el MIN (marca de tiempo). Lo que dará como resultado la consulta de esta manera sigue siendo un recorrido por todas las filas (esto no se puede evitar, obviamente), pero puede hacerlo por orden de índice, generar una fila para cada agrupación a medida que llega al siguiente valor en el índice en lugar de tener que recordar todo el conjunto de filas para una operación de clasificación no indexada antes de que se pueda realizar la agrupación / agregación. También usará mucha menos memoria, ya que no necesitará recordar ninguna fila de valores de agrupación anteriores para procesar la que está viendo ahora o el resto de ellas.

Ese método elimina la necesidad de encontrar algún lugar en la memoria para todo el conjunto de resultados y realiza la ordenación no indexada para la operación del grupo y elimina el cálculo de los valores del grupo fuera de la consulta grande (mover ese trabajo a los INSERTOS / ACTUALIZACIONES individuales que producen el datos) y debería permitir que dichas consultas se ejecuten de manera aceptable sin necesidad de mantener un almacén separado de los resultados agregados.

Un método que nodesnormalizar sus datos, pero aún requiere una estructura adicional, es usar una "tabla de tiempos", en este caso una que contenga una fila por hora durante todo el tiempo que probablemente tenga en cuenta. Esta tabla no consumiría una cantidad significativa de espacio en una base de datos o un tamaño apreciable: para cubrir un intervalo de tiempo de 100 años, una tabla que contiene una fila de dos fechas (el inicio y el final de la hora, como '2011-01-01 @ 00: 00: 00.0000 ',' 2011-01-01 @ 00: 00: 59.9997 ', siendo el "9997" el menor número de milisegundos que un campo DATETIME no se redondeará al siguiente segundo), que son parte del la clave primaria en clúster ocupará ~ 14Mbyte de espacio (8 + 8 bytes por fila * 24 horas / día * 365.25 días / año * 100, más un poco para la sobrecarga de la estructura de árbol del índice agrupado, pero esa sobrecarga no 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

Esto significa que el planificador de consultas puede organizar el uso del índice en MyData.TimeStamp. El planificador de consultas debe ser lo suficientemente brillante como para poder descifrar la tabla de domesticación en el paso con el índice en MyData.TimeStamp, generando nuevamente una fila por agrupación y descartando cada conjunto o filas cuando alcanza el siguiente valor de agrupación. No almacenar todas las filas intermedias en algún lugar de la RAM y luego realizar una ordenación no indexada en ellas. Por supuesto, este método requiere que cree la tabla de tiempo y asegúrese de que se extienda lo suficiente tanto hacia atrás como hacia adelante, pero puede usar la tabla de tiempo para consultas en muchos campos de fecha en consultas diferentes, donde la opción "columna adicional" requeriría una columna calculada adicional para cada campo de fecha que necesitaba filtrar / agrupar de esta manera, y el tamaño pequeño de la tabla (a menos que lo necesite para abarcar 10,

El método de la tabla de tiempos tiene una diferencia adicional (que podría ser bastante ventajosa) en comparación con su situación actual y la solución de columna calculada: puede devolver filas para períodos para los que no hay datos, simplemente cambiando INNER JOIN en la consulta de ejemplo anterior ser una IZQUIERDA EXTERIOR.

Algunas personas sugieren no tener un horario físico, sino que siempre lo devuelven desde una función de retorno de la tabla. Esto significa que el contenido de la tabla de tiempos nunca se almacena (o necesita leerse) en el disco y si la función está bien escrita, nunca tendrá que preocuparse por cuánto tiempo la tabla de tiempos debe extenderse de un lado a otro, pero yo dudar del costo de CPU de producir una tabla en memoria para algunas filas, cada consulta vale la pena ahorrar un poco de la molestia de crear (y mantener, si su intervalo de tiempo necesita extenderse más allá del límite de su versión inicial) la tabla de tiempo física.

Una nota al margen: tampoco necesita esa cláusula DISTINCT en su consulta original. La agrupación asegurará que estas consultas solo devuelvan una fila por período en consideración, por lo que DISTINCT no hará nada más que girar la CPU un poco más (a menos que el planificador de consultas advierta que la distinción sería un no-op en cuyo caso ignórelo y no use tiempo de CPU adicional).

David Spillett
fuente
3

Vea esta pregunta ( piso una fecha ). Además, ¿por qué molestarse en convertir todo en cadena? Puede hacerlo más tarde (si es necesario).

  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
fuente
1

¿Desea acelerar la consulta o pregunta cómo hacer una instantánea de datos y guardarla?

Si desea hacerlo más rápido, definitivamente necesita un índice en el campo TimeStamp. Además, sugeriría usar esto para convertir a hora:

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

Si necesita hacer una instantánea y reutilizarla más tarde, úsela insert intopara crear una nueva tabla con los resultados de su consulta. Indice la tabla de acuerdo y úsela. Según tengo entendido, necesitará un índice en TimeStampHour.

También puede configurar un trabajo que agregue datos diarios en su nueva tabla agregada.

Alex Aza
fuente
-1

Al convertir su grupo por cláusula en una cadena como esa, esencialmente lo está convirtiendo en un hit no indexado para cada fila de la base de datos. Esto es lo que está matando tu rendimiento. Cualquier servidor medio decente podrá manejar un agregado simple como ese en un millón de registros, muy bien si los índices se usan correctamente. Modificaría su consulta y pondría un índice agrupado en sus marcas de tiempo. Eso resolverá su problema de rendimiento, mientras que calcular los datos cada hora es solo posponer el problema.


fuente
1
-1: no, no estás "convirtiéndolo en un hit no indexado para cada fila de la base de datos" - cualquier índice TimeStamp
activado
-3

Consideraría abandonar la idea de implementar este tipo de cálculo utilizando un modelo de base de datos relacional. Especialmente si tiene muchos puntos de datos para los cuales recopila valores cada segundo.

Si tiene el dinero, podría considerar comprar un historiador de datos de proceso dedicado como:

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

Estos productos pueden almacenar grandes cantidades de datos de series de tiempo increíblemente densos (en formatos propietarios) al tiempo que permiten el procesamiento rápido de consultas de extracción de datos. Las consultas pueden especificar muchos puntos de datos (también llamados etiquetas), largos intervalos de tiempo (meses / años), y además pueden hacer una amplia variedad de cálculos de datos de resumen (incluidos promedios).

.. y en una nota general: siempre trato de evitar usar la DISTINCTpalabra clave cuando escribo SQL. Casi nunca es una buena idea. En su caso, debería poder soltar DISTINCTy obtener los mismos resultados agregando MIN([timestamp])a su GROUP BYcláusula.


fuente
1
Esto no es realmente exacto. Una base de datos relacional está perfectamente bien para 2.5 millones de registros. Y ni siquiera está haciendo uniones en muchas mesas. La primera indicación de que necesita desnormalizar sus datos o pasar a un sistema no relacional es cuando está haciendo uniones grandes y complejas en muchas tablas. El conjunto de datos del póster en realidad suena como un uso perfectamente aceptable de un sistema de base de datos relacional.