¿Por qué esta consulta no utiliza mi índice no agrupado y cómo puedo hacerlo?

12

Como seguimiento a esta pregunta sobre el aumento del rendimiento de las consultas, me gustaría saber si hay una manera de hacer que mi índice se use de forma predeterminada.

Esta consulta se ejecuta en aproximadamente 2.5 segundos:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

Este se ejecuta en unos 33 ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 
ORDER BY [DateEntered], [DeviceID];

Hay un índice agrupado en el campo [ID] (pk) y hay un índice no agrupado en [DateEntered], [DeviceID]. La primera consulta usa el índice agrupado, la segunda consulta usa mi índice no agrupado. Mi pregunta es de dos partes:

  • ¿Por qué, dado que ambas consultas tienen una cláusula WHERE en el campo [DateEntered], el servidor usa el índice agrupado en la primera, pero no en la segunda?
  • ¿Cómo puedo hacer que el índice no agrupado se use de forma predeterminada en esta consulta incluso sin el pedido? (¿O por qué no querría ese comportamiento?)
Nate
fuente
DateEntered es un DateTime, en este caso estoy usando la parte de fecha, pero a veces consulto tanto la fecha como la hora juntas.
Nate

Respuestas:

9

la primera consulta escanea una tabla en función del umbral que expliqué anteriormente: ¿Es posible aumentar el rendimiento de la consulta en una tabla estrecha con millones de filas?

(lo más probable es que su consulta sin la TOP 1000cláusula devuelva más de 46k filas. o alguna entre 35k y 46k. (el área gris ;-))

la segunda consulta, debe ser ordenada. Como su índice NC está ordenado en el orden que desea, es más barato para el optimizador usar ese índice, y luego buscar marcadores en el índice agrupado para obtener las columnas que faltan en comparación con hacer un escaneo de índice agrupado y luego necesitar para ordenar eso.

invierta el orden de las columnas en la ORDER BYcláusula y volverá a una exploración de índice agrupado ya que el INDICE NC es inútil.

edit olvidó la respuesta a su segunda pregunta, ¿por qué NO quiere esto?

El uso de un índice de cobertura no agrupado significa que se busca un ID de fila en el índice NC y luego las columnas que faltan deben buscarse en el índice agrupado (el índice agrupado contiene todas las columnas de una tabla). Las E / S para buscar las columnas que faltan en el índice agrupado son E / S aleatorias.

La clave aquí es ALEATORIO. porque por cada fila encontrada en el índice NC, los métodos de acceso tienen que buscar una nueva página en el índice agrupado. Esto es aleatorio y, por lo tanto, muy costoso.

Ahora, por otro lado, el optimizador también podría optar por un escaneo de índice agrupado. Puede usar los mapas de asignación para buscar rangos de escaneo y simplemente comenzar a leer el índice agrupado en fragmentos grandes. Esto es secuencial y mucho más barato. (siempre y cuando su tabla no esté fragmentada :-)) La desventaja es que TODO el índice agrupado debe leerse. Esto es malo para su búfer y potencialmente una gran cantidad de IO. pero aún así, IO secuenciales.

En su caso, el optimizador decide en algún lugar entre 35k y 46k filas, es menos costoso para un escaneo de índice agrupado completo. Si, esta mal. Y en muchos casos con índices estrechos no agrupados con WHEREcláusulas no selectivas o una tabla grande para el caso, esto sale mal. (Su mesa es peor, porque también es una mesa muy estrecha).

Ahora, agregarlo ORDER BYhace que sea más costoso escanear el índice agrupado completo y luego ordenar los resultados. En cambio, el optimizador supone que es más barato usar el índice NC ya ordenado y luego pagar la penalización de E / S aleatoria por las búsquedas de marcadores.

Por lo tanto, su pedido es una solución de tipo "sugerencia de consulta" perfecta. PERO, en cierto punto, una vez que los resultados de su consulta son tan grandes, la penalización por las E / S aleatorias de búsqueda de marcadores será tan grande que se volverá más lenta. Supongo que el optimizador cambiará los planes al análisis de índice agrupado antes de ese punto, pero nunca se sabe con certeza.

En su caso, siempre que sus inserciones estén ordenadas por enterdate, como se discutió en el chat y la pregunta anterior (ver enlace), es mejor que cree el índice agrupado en la columna enterDate.

Edward Dortland
fuente
20

Expresar la consulta usando una sintaxis diferente a veces puede ayudar a comunicar su deseo de usar un índice no agrupado al optimizador. Debe encontrar el formulario a continuación que le brinda el plan que desea:

SELECT
    [ID],
    [DeviceID],
    [IsPUp],
    [IsWebUp],
    [IsPingUp],
    [DateEntered]
FROM [dbo].[Heartbeats]
WHERE
    [ID] IN
(
    -- Keys
    SELECT TOP (1000)
        [ID]
    FROM [dbo].[Heartbeats]
    WHERE 
        [DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
        AND [DateEntered]  < CONVERT(datetime, '2011-08-31', 121)
);

Plan de consulta

Compare ese plan con el producido cuando el índice no agrupado se fuerza con una pista:

SELECT TOP (1000) 
    * 
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE 
    [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

Plan de sugerencias de índice forzado

Los planes son esencialmente los mismos (una búsqueda clave no es más que una búsqueda en el índice agrupado). Ambas formas de plan solo realizarán una búsqueda en el índice no agrupado y un máximo de 1000 búsquedas en el índice agrupado.

La diferencia importante está en la posición del operador superior. Posicionado entre las dos búsquedas, la parte superior evita que el optimizador reemplace las dos operaciones de búsqueda con un escaneo lógicamente equivalente del índice agrupado. El optimizador funciona reemplazando partes de un plan lógico con operaciones relacionales equivalentes. Top no es un operador relacional, por lo que la reescritura evita la transformación a un escaneo de índice agrupado. Si el optimizador pudiera reposicionar el operador Top, aún preferiría el escaneo a la búsqueda + búsqueda debido a la forma en que funciona la estimación de costos.

Costeo de escaneos y búsquedas

En un nivel muy alto, el modelo de costo del optimizador para escaneos y búsquedas es bastante simple: estima que 320 búsquedas aleatorias cuestan lo mismo que leer 1350 páginas en un escaneo. Esto probablemente se parece poco a las capacidades de hardware de cualquier sistema de E / S moderno en particular, pero funciona razonablemente bien como modelo práctico.

El modelo también hace una serie de suposiciones simplificadoras, una de las cuales es que se supone que cada consulta comienza sin datos o páginas de índice ya en caché. La implicación es que cada E / S dará como resultado una E / S física, aunque esto rara vez será el caso en la práctica. Incluso con un caché frío, la búsqueda previa y la lectura anticipada significan que las páginas necesarias en realidad es probable que estén en la memoria cuando el procesador de consultas las necesite.

Otra consideración es que la primera solicitud de una fila que no está en la memoria hará que toda la página se recupere del disco. Las solicitudes posteriores de filas en la misma página probablemente no incurrirán en una E / S física. El modelo de costeo contiene lógica para tener en cuenta efectos como este, pero no es perfecto.

Todas estas cosas (y más) significa que el optimizador tiende a cambiar a un escaneo antes de lo que probablemente debería. La E / S aleatoria es solo "mucho más costosa" que la E / S "secuencial" si se produce una operación física: el acceso a las páginas en la memoria es realmente muy rápido. Incluso cuando se requiere una lectura física, un escaneo puede no resultar en lecturas secuenciales debido a la fragmentación, y las búsquedas pueden ser colocadas de manera tal que el patrón sea esencialmente secuencial. Agregue a eso la característica de rendimiento cambiante de los sistemas modernos de E / S (especialmente de estado sólido) y todo comienza a verse muy inestable.

Objetivos de fila

La presencia de un operador Top en un plan modifica el enfoque de costos. El optimizador es lo suficientemente inteligente como para saber que encontrar 1000 filas usando un escaneo probablemente no requerirá escanear todo el índice agrupado; puede detenerse tan pronto como se hayan encontrado 1000 filas. Establece un 'objetivo de fila' de 1000 filas en el operador Superior y usa información estadística para trabajar desde allí para estimar cuántas filas espera necesitar del origen de la fila (una exploración en este caso). Escribí sobre los detalles de este cálculo aquí .

Las imágenes de esta respuesta se crearon con el Explorador de planes SQL Sentry .

Paul White 9
fuente