sql >> Base de Datos >  >> RDS >> Database

Índices filtrados y columnas INCLUIDAS

Los índices filtrados son increíblemente poderosos, pero aún veo cierta confusión sobre ellos, particularmente sobre las columnas que se usan en los filtros y lo que sucede cuando desea ajustar los filtros.

Una pregunta reciente en dba.stackexchange solicitó ayuda sobre por qué las columnas utilizadas en el filtro de un índice filtrado deben incluirse en las columnas "incluidas" del índice. Excelente pregunta, excepto que sentí que comenzó con una premisa pobre, porque esas columnas no deberían incluirse en el índice . Sí, ayudan, pero no en la forma en que la pregunta parecía sugerir.

Para evitar tener que mirar la pregunta en sí, aquí hay un resumen rápido:

Para satisfacer esta consulta...

SELECT Id, DisplayName 
FROM Users 
WHERE Reputation > 400000;

…el siguiente índice filtrado es bastante bueno:

CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club
ON dbo.Users ( DisplayName, Id )
INCLUDE ( Reputation )
WHERE Reputation > 400000;

Pero a pesar de tener este índice en su lugar, el Optimizador de consultas recomienda el siguiente índice si el valor filtrado se ajusta a, digamos, 450000.

CREATE NONCLUSTERED INDEX IndexThatWasMissing
ON dbo.Users ( Reputation )
INCLUDE ( DisplayName, Id );

Estoy parafraseando un poco la pregunta aquí, que comienza refiriéndose a esta situación y luego construye un ejemplo diferente, pero la idea es la misma. Simplemente no quería complicar las cosas al involucrar una mesa separada.

El punto es:el índice sugerido por el QO es el índice original, pero se le dio la vuelta. El índice original tenía Reputación en la lista INCLUDE y DisplayName e Id como columnas clave, mientras que el nuevo índice recomendado es al revés con Reputación como columna clave y DisplayName e ID en INCLUDE. Veamos por qué.

La pregunta hace referencia a una publicación de Erik Darling, donde explica que ajustó la consulta '450,000' anterior al colocar Reputación en la columna INCLUIR. Erik muestra que sin Reputación en la lista INCLUDE, una consulta que filtra a un valor más alto de Reputación necesita realizar búsquedas (¡mal!), o tal vez incluso renunciar por completo al índice filtrado (potencialmente incluso peor). Concluye que tener la columna Reputación en la lista INCLUDE le permite a SQL tener estadísticas, para que pueda tomar mejores decisiones, y muestra que con Reputación en INCLUDE una variedad de consultas que filtran valores de Reputación más altos y escanean su índice filtrado.

En una respuesta a la pregunta de dba.stackexchange, Brent Ozar señala que las mejoras de Erik no son particularmente buenas porque provocan escaneos. Volveré a eso, porque es un punto interesante en sí mismo y algo incorrecto.

Primero, pensemos un poco sobre los índices en general.

Un índice proporciona una estructura ordenada a un conjunto de datos. (Podría ser pedante y señalar que leer los datos en un índice de principio a fin puede llevarlo de una página a otra de una manera aparentemente desordenada, pero aun así, mientras lee las páginas, sigue los punteros de una página a otra). el siguiente puede estar seguro de que los datos están ordenados. Dentro de cada página, incluso puede saltar para leer los datos en orden, pero hay una lista que le muestra qué partes (ranuras) de la página deben leerse en qué orden. Realmente hay no tiene sentido en mi pedantería excepto responder a aquellos igualmente pedantes que comentarán si no lo hago).

Y este orden es de acuerdo con las columnas clave:esa es la parte fácil que todos obtienen. Es útil no solo para poder evitar reordenar los datos más tarde, sino también para poder ubicar rápidamente cualquier fila o rango de filas en particular por esas columnas.

Los niveles de hoja del índice contienen los valores de cualquier columna de la lista INCLUDE o, en el caso de un índice agrupado, los valores de todas las columnas de la tabla (excepto las columnas calculadas no persistentes). Los otros niveles en el índice contienen solo las columnas clave y (si el índice no es único) la dirección única de la fila, que son las claves del índice agrupado (con el uniquifier de la fila si el índice agrupado tampoco es único). ) o el valor RowID para un montón, suficiente para permitir un fácil acceso a todos los demás valores de columna para la fila. Los niveles de hoja también incluyen toda la información de "dirección".

Pero eso no es lo interesante de este post. Lo interesante de esta publicación es lo que quiero decir con "a un conjunto de datos". Recuerde que dije "Un índice proporciona una estructura ordenada a un conjunto de datos ".

En un índice agrupado, ese conjunto de datos es la tabla completa, pero podría ser otra cosa. Probablemente ya pueda imaginar cómo la mayoría de los índices no agrupados no involucran todas las columnas de la tabla. Esta es una de las cosas que hace que los índices no agrupados sean tan útiles, porque normalmente son mucho más pequeños que la tabla subyacente.

En el caso de una vista indexada, nuestro conjunto de datos podría ser el resultado de una consulta completa, incluidas las uniones en muchas tablas. Eso es para otra publicación.

Pero en un índice filtrado, no es solo una copia de un subconjunto de columnas, sino también un subconjunto de filas. Entonces, en el ejemplo aquí, el índice es solo para los usuarios con más de 400k de reputación.

CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club_NoInclude
ON dbo.Users ( DisplayName, Id )
WHERE Reputation > 400000;

Este índice toma a los usuarios que tienen más de 400k de reputación y los ordena por DisplayName e Id. Puede ser único porque (supuestamente) la columna Id ya es única. Si intenta algo similar en su propia mesa, es posible que deba tener cuidado con eso.

Pero en este punto, al índice no le importa cuál es la Reputación para cada usuario, solo le importa si la Reputación es lo suficientemente alta como para estar en el índice o no. Si la reputación de un usuario se actualiza y supera el umbral, el nombre para mostrar y la identificación del usuario se insertarán en el índice. Si cae por debajo, se eliminará del índice. Es como tener una mesa separada para los grandes apostadores, excepto que colocamos a las personas en esa mesa aumentando su valor de Reputación por encima del umbral de 400k en la tabla subyacente. Puede hacer esto sin tener que almacenar el valor de Reputación.

Entonces, ahora, si queremos encontrar personas que tengan un umbral superior a 450k, a ese índice le falta algo de información.

Claro, podríamos decir con confianza que todos los que encontraremos están en ese índice, pero el índice no contiene suficiente información en sí mismo para filtrar más en Reputación. Si te dijera que tengo una lista alfabética de las películas ganadoras del Oscar a la Mejor Película de la década de 1990 (American Beauty, Braveheart, Dances With Wolves, English Patient, Forrest Gump, Schindler's List, Shakespeare in Love, Silence of the Lambs, Titanic, Unforgiven) , entonces puedo asegurarle que los ganadores de 1994-1996 serían un subconjunto de esos, pero no puedo responder la pregunta sin antes obtener más información.

Obviamente, mi índice filtrado sería más útil si hubiera incluido el año, y potencialmente aún más si el año fuera una columna clave, ya que mi nueva consulta quiere encontrar las de 1994-1996. Pero probablemente diseñé este índice en torno a una consulta para enumerar todas las películas de la década de 1990 en orden alfabético. A esa consulta no le importa cuál es el año real, solo si está en la década de 1990 o no, y ni siquiera necesito devolver el año, solo el título, así que puedo escanear mi índice filtrado para obtener los resultados. Para esa consulta, ni siquiera necesito reordenar los resultados o encontrar el punto de partida; mi índice es realmente perfecto.

Un ejemplo más práctico de no preocuparse por el valor de la columna en el filtro es el estado, como:

WHERE IsActive = 1

Con frecuencia veo código que mueve datos de una tabla a otra cuando las filas dejan de estar 'activas'. Las personas no quieren que las filas antiguas llenen su tabla y reconocen que sus datos "calientes" son solo un pequeño subconjunto de todos sus datos. Entonces mueven sus datos de enfriamiento a una tabla de archivo, manteniendo pequeña su tabla activa.

Un índice filtrado puede hacer esto por usted. Entre bastidores. Tan pronto como actualice la fila y cambie esa columna IsActive a algo que no sea 1. Si solo le importa tener datos activos en la mayoría de sus índices, entonces los índices filtrados son ideales. Incluso devolverá las filas a los índices si el valor de IsActive vuelve a cambiar a 1.

Pero no necesita poner IsActive en la lista INCLUDE para lograr esto. ¿Por qué querrías almacenar el valor? Ya sabes cuál es el valor:¡es 1! A menos que esté pidiendo devolver el valor, no debería necesitarlo. ¿Y por qué devolverías el valor cuando ya sabes que la respuesta es 1, verdad? Excepto que, de manera frustrante, las estadísticas a las que Erik se refiere en su publicación aprovecharán estar en la lista INCLUIR. No lo necesita para la consulta, pero debe incluirlo para las estadísticas.

Pensemos en lo que debe hacer el Optimizador de consultas para determinar la utilidad de un índice.

Antes de que pueda hacer mucho, debe considerar si el índice es un candidato. No tiene sentido usar un índice si no tiene todas las filas que podrían ser necesarias, a menos que tengamos una forma efectiva de obtener el resto. Si quiero películas de 1985 a 1995, entonces mi índice de películas de la década de 1990 es bastante inútil. Pero para 1994-1996, tal vez no esté mal.

En este punto, al igual que cualquier consideración de índice, debo pensar si ayudará lo suficiente para encontrar los datos y ponerlos en un orden que ayude a ejecutar el resto de la consulta (posiblemente para un Merge Join, Stream Aggregate, que satisfaga un PEDIDO POR u otras razones). Si mi filtro de consulta coincide exactamente con el filtro de índice, entonces no necesito filtrar más, solo usar el índice es suficiente. Esto suena genial, pero si no coincide exactamente, si mi filtro de consulta es más estricto que el filtro de índice (como mi ejemplo de 1994-1996, o los 450 000 de Erik), necesitaré tener esos valores de año o valores de reputación. para verificar, con suerte obtenerlos de INCLUIDO en el nivel de hoja o en algún lugar de mis columnas clave. Si no están en el índice, tendré que hacer una búsqueda para cada fila en mi índice filtrado (e idealmente, tener una idea de cuántas veces se llamará a mi búsqueda, cuáles son las estadísticas que Erik quiere la columna incluida para).

Idealmente, cualquier índice que planee usar está ordenado correctamente (a través de las claves), INCLUYE todas las columnas que necesito devolver y está prefiltrado solo para las filas que necesito. Ese sería el índice perfecto y mi plan de ejecución será un escaneo.

Así es, un SCAN. No es un Seek, sino un Scan. Comenzará en la primera página de mi índice y seguirá dándome filas hasta que tenga todas las que necesito, o hasta que no haya más filas para devolver. Sin omitir ninguno, sin ordenarlos, solo dándome las filas en orden.

Un Seek sugeriría que no necesito todo el índice, lo que significa que estoy desperdiciando recursos en el mantenimiento de esa parte del índice, y para consultarlo tengo que encontrar el punto de partida y seguir revisando las filas para ver si tengo llegar al final o no. Si mi escaneo tiene un predicado, entonces seguro, tengo que revisar (y probar) más datos de los que necesito, pero si mis filtros de índice son perfectos, entonces el Optimizador de consultas debería reconocerlo y no tener que realizar esas comprobaciones. .

Reflexiones finales

Los INCLUDE no son críticos para los índices filtrados. Son útiles para proporcionar un fácil acceso a las columnas que podrían ser útiles para su consulta, y si está restringiendo lo que hay en su índice filtrado por cualquier columna, ya sea que se mencione en el filtro o no, debería considerar tener esa columna en la mezcla. Pero en ese momento debería preguntarse si el filtro de su índice es el correcto, qué más debería tener en su lista INCLUYE e incluso cuáles deberían ser las columnas clave. Las consultas de Erik no funcionaban bien porque necesitaba información que no estaba en el índice, a pesar de que había mencionado la columna en el filtro. También encontró un buen uso para las estadísticas, y aún así lo animo a incluir las columnas de filtro por ese motivo. Pero ponerlos en un INCLUDE no les permite comenzar repentinamente a hacer una búsqueda, porque no es así como funciona cualquier índice, ya sea filtrado o no.

Quiero que usted, lector, comprenda muy bien los índices filtrados. Son increíblemente útiles y, cuando comienza a imaginarlos como tablas por derecho propio, pueden convertirse en parte del diseño general de su base de datos. También son una razón para usar siempre las configuraciones ANSI_NULL y QUOTED_IDENTIFIER, porque obtendrá errores del índice filtrado a menos que esas configuraciones estén ACTIVADAS, pero espero que ya se asegure de que siempre estén activadas de todos modos.

Ah, y esas películas eran Forrest Gump, Braveheart y El paciente inglés.

@rob_farley