Tengo una consulta que actualmente tarda un promedio de 2500 ms en completarse. Mi mesa es muy estrecha, pero hay 44 millones de filas. ¿Qué opciones tengo para mejorar el rendimiento, o es tan bueno como es posible?
La consulta
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';
La mesa
CREATE TABLE [dbo].[Heartbeats](
[ID] [int] IDENTITY(1,1) NOT NULL,
[DeviceID] [int] NOT NULL,
[IsPUp] [bit] NOT NULL,
[IsWebUp] [bit] NOT NULL,
[IsPingUp] [bit] NOT NULL,
[DateEntered] [datetime] NOT NULL,
CONSTRAINT [PK_Heartbeats] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
El índice
CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats]
(
[DateEntered] ASC,
[DeviceID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
¿Sería útil agregar índices adicionales? Si es así, ¿cómo se verían? El rendimiento actual es aceptable, porque la consulta solo se ejecuta ocasionalmente, pero me pregunto como ejercicio de aprendizaje, ¿hay algo que pueda hacer para que esto sea más rápido?
ACTUALIZAR
Cuando cambio la consulta para usar una pista de índice de fuerza, la consulta se ejecuta en 50 ms:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats] WITH(INDEX(CommonQueryIndex))
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'
Agregar una cláusula DeviceID correctamente selectiva también alcanza el rango de 50 ms:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' AND DeviceID = 4;
Si agrego ORDER BY [DateEntered], [DeviceID]
a la consulta original, estoy en el rango de 50 ms:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'
ORDER BY [DateEntered], [DeviceID];
Todos usan el índice que esperaba (CommonQueryIndex), así que supongo que mi pregunta es ahora, ¿hay alguna manera de forzar este índice para que se use en consultas como esta? ¿O es que el tamaño de mi mesa arroja demasiado el optimizador y debo usar una ORDER BY
o una pista?
Respuestas:
Por qué el optimizador no va para tu primer índice:
Es una cuestión de selectividad de la columna [DateEntered].
Nos dijo que su mesa tiene 44 millones de filas. el tamaño de la fila es:
4 bytes, para la ID, 4 bytes para la ID del dispositivo, 8 bytes para la fecha y 1 byte para las columnas de 4 bits. eso es 17 bytes + 7 bytes de sobrecarga para (etiquetas, mapa de bits nulo, desplazamiento de col variable, recuento de col) totaliza 24 bytes por fila.
Eso se traduciría a 140k páginas. Para almacenar esos 44 millones de filas.
Ahora el optimizador puede hacer dos cosas:
Ahora, en cierto punto, se vuelve más costoso hacer todas estas búsquedas individuales en el índice agrupado para cada entrada de índice encontrada en su índice no agrupado. El umbral para eso es generalmente el recuento total de búsquedas debe exceder del 25% al 33% del recuento total de páginas de la tabla.
Entonces, en este caso: 140k / 25% = 35000 filas 140k / 33% = 46666 filas.
(@RBarryYoung, 35k es 0.08% del total de filas y 46666 es 0.10%, así que creo que ahí es donde estaba la confusión)
Entonces, si su cláusula where resultará en algún lugar entre las filas 35000 y 46666 (¡esto está debajo de la cláusula superior!) Es muy probable que su no agrupado no se use y que se use el escaneo de índice agrupado.
Las únicas dos formas de cambiar esto son:
ahora seguro de que puede crear un índice de cobertura incluso cuando usa un select *. Sin embargo, eso solo crea una sobrecarga masiva para sus inserciones / actualizaciones / eliminaciones. Tendríamos que saber más sobre su carga de trabajo (lectura vs escritura) para asegurarnos de que esa sea la mejor solución.
Cambiar de datetime a smalldatetime es una reducción del tamaño del 16% en el índice agrupado y una reducción del tamaño del 24% en el índice no agrupado.
fuente
¿Hay alguna razón particular por la que su PK esté agrupado? Muchas personas hacen esto porque su valor predeterminado es ese, o piensan que las PK deben agruparse. No es asi. Los índices agrupados suelen ser mejores para consultas de rango (como este) o en la clave externa de una tabla secundaria.
Un efecto de un índice de agrupación es que agrupa todos los datos juntos porque los datos se almacenan en los nodos hoja del árbol de agrupación b. Entonces, suponiendo que no está pidiendo un rango 'demasiado amplio', el optimizador sabrá exactamente qué parte del árbol b contiene los datos y no tendrá que encontrar un identificador de fila y luego saltar a donde los datos es (como lo hace cuando se trata de un índice NC). ¿Qué es "demasiado amplio" de un rango? Un ejemplo ridículo sería pedir 11 meses de datos de una tabla que solo tiene un año de registros. Obtener un día de datos no debería ser un problema, suponiendo que sus estadísticas estén actualizadas. (Sin embargo, el optimizador puede meterse en problemas si está buscando los datos de ayer y no ha actualizado las estadísticas durante tres días).
Como está ejecutando una consulta "SELECCIONAR *", el motor deberá devolver todas las columnas de la tabla (incluso si alguien agrega una nueva que su aplicación no necesita en ese momento), de modo que un índice de cobertura o un índice con columnas incluidas no ayudará mucho, si es que lo hace. (Si incluye todas las columnas de la tabla en un índice, está haciendo algo mal). El optimizador probablemente ignorará esos índices NC.
¿Entonces lo que hay que hacer?
Mi sugerencia sería eliminar el índice NC, cambiar el PK agrupado a no agrupado y crear un índice agrupado en [DateEntered]. Más simple es mejor, hasta que se demuestre lo contrario.
fuente
Mientras tenga ese "*" allí, lo único que podría imaginar que marcaría una gran diferencia sería cambiar su definición de índice a esto:
Como señalé en los comentarios, debería usar ese índice, pero si no lo hace, puede persuadirlo con ORDER BY o una pista de índice.
fuente
Vería esto un poco diferente.
Volcaría la columna de fecha y hora; cámbiela por una int. Tener una tabla de búsqueda o hacer una conversión para su fecha.
Volcar el índice agrupado: déjelo como un montón y cree un índice no agrupado en la nueva columna INT que representa la fecha. es decir, hoy sería 20121015. Ese orden es importante. Según la frecuencia con la que cargue la tabla, busque crear ese índice en orden DESC. El costo de mantenimiento será más alto y querrá introducir un factor de relleno o partición. Particionar también ayudaría a disminuir el tiempo de ejecución.
Por último, si puede usar SQL 2012, intente usar SECUENCIA: superará la identidad () para las inserciones.
fuente