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
Respuestas:
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) .
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).
fuente
Vea esta pregunta ( piso una fecha ). Además, ¿por qué molestarse en convertir todo en cadena? Puede hacerlo más tarde (si es necesario).
fuente
¿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:
Si necesita hacer una instantánea y reutilizarla más tarde, úsela
insert into
para 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.
fuente
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
TimeStamp
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:
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
DISTINCT
palabra clave cuando escribo SQL. Casi nunca es una buena idea. En su caso, debería poder soltarDISTINCT
y obtener los mismos resultados agregandoMIN([timestamp])
a suGROUP BY
cláusula.fuente