¿Por qué mi cláusula WHERE se beneficia de una columna "incluida"?

12

Según esta respuesta , a menos que se construya un índice sobre las columnas que se usan para restringir, la consulta no se beneficiará de un índice.

Tengo esta definición:

CREATE TABLE [dbo].[JobItems] (
    [ItemId]             UNIQUEIDENTIFIER NOT NULL,
    [ItemState]          INT              NOT NULL,
    [ItemPriority]       INT NOT NULL,
    [CreationTime]       DATETIME         NULL DEFAULT GETUTCDATE(),
    [LastAccessTime]     DATETIME         NULL DEFAULT GETUTCDATE(),
     -- other columns
 );

 CREATE UNIQUE CLUSTERED INDEX [JobItemsIndex]
    ON [dbo].[JobItems]([ItemId] ASC);
 GO

CREATE INDEX [GetItemToProcessIndex]
    ON [dbo].[JobItems]([ItemState], [ItemPriority], [CreationTime])
    INCLUDE (LastAccessTime);
GO

y esta consulta:

UPDATE TOP (150) JobItems 
SET ItemState = 17 
WHERE 
    ItemState IN (3, 9, 10)
    AND LastAccessTime < DATEADD (day, -2, GETUTCDATE()) 
    AND CreationTime < DATEADD (day, -2, GETUTCDATE());

Revisé el plan real, y solo hay una búsqueda de índice con el predicado exactamente como en el WHERE- no hay "búsquedas de marcadores" adicionales para recuperar LastAccessTimea pesar de que este último solo está "incluido" en el índice, no es parte del índice.

Me parece que este comportamiento contradice la regla de que la columna debe ser parte del índice, y no solo "incluida".

¿El comportamiento que observo es el correcto? ¿Cómo puedo saber de antemano si mis WHEREbeneficios de una columna incluida o si la columna forma parte del índice?

diente filoso
fuente
Todavía puede buscar en función del ItemStatevalor, sin embargo, Seek no será tan eficiente como si su índice estuviera estructurado de la siguiente manera(ItemState, CreationTime, LastAccessTime)
Mark Sinkinson
1
@MarkSinkinson o simplemente(ItemState, CreationTime) INCLUDE (LastAccessTime)
ypercubeᵀᴹ
@sharptooth la respuesta vinculada que tiene no dice eso ("a menos que se construya un índice sobre las columnas que se utilizan para restringir la consulta no se beneficiará de un índice"). Dice que un índice activado (a,b)no es el mejor para una consulta SELECT a FROM t WHERE b=5;y que un índice activado (b) INCLUDE (a)es mucho mejor.
ypercubeᵀᴹ

Respuestas:

9

Su predicado es diferente a su predicado de búsqueda.

Se utiliza un predicado de búsqueda para buscar los datos ordenados en el índice. En este caso, estará haciendo tres búsquedas, una para cada ItemState que le interese. Más allá de eso, los datos están en orden de Prioridad de Item, por lo que no se puede realizar ninguna operación de "Búsqueda".

Pero antes de que se devuelvan los datos, verifica cada fila utilizando el predicado, al que me refiero como el predicado residual. Se hace con los resultados del predicado de búsqueda.

Cualquier columna incluida no es parte de los datos ordenados, pero se puede utilizar para satisfacer el predicado residual, sin tener que hacer la búsqueda adicional.

Puedes ver material que he escrito sobre esto en torno a Sargability. Busque una sesión en SQLBits en particular, en http://bit.ly/Sargability

Editar: para mostrar mejor el impacto de los Residuos, ejecute la consulta usando el indocumentado OPTION (QUERYTRACEON 9130), que separará el Residual en un operador de Filtro separado (que en realidad es una versión anterior del plan antes de que el residual se traslade al operador de Búsqueda). Muestra claramente el impacto de una Búsqueda ineficaz, por el número de filas que se pasan a la izquierda al Filtro.

También vale la pena señalar que debido a la cláusula IN en ItemState, los datos que se pasan a la izquierda están realmente en orden ItemState, no en orden OrderPriority. Se podría usar un índice compuesto en ItemState seguido de una de las fechas (por ejemplo, (ItemState, LastAccessTime)) para tener tres búsquedas (observe que el predicado de búsqueda muestra tres búsquedas dentro del operador de búsqueda), cada una contra dos niveles, produciendo datos que son todavía en orden ItemState (por ejemplo, ItemState = 3 y LastAccessTime menos que algo, luego ItemState = 9 y LastAccessTime menos que algo, y luego ItemState = 10 y LastAccessTime menos que algo).

Un índice en (ItemState, LastAccesTime, CreationTime) no sería más útil que uno en (ItemState, LastAccessTime) porque el nivel CreationTime solo es útil si su búsqueda es para una combinación particular de ItemState y LastAccessTime, no un rango. Por ejemplo, si la guía telefónica no está en orden de nombre si está interesado en los apellidos que comienzan en F.

Si desea un índice compuesto pero nunca podrá usar las columnas posteriores en Buscar predicados debido a la forma en que usa las columnas anteriores, entonces también puede tenerlas como columnas incluidas, donde ocupan menos espacio en el índice (porque solo se almacenan en el nivel de hoja del índice, no en los niveles superiores) pero aún pueden evitar búsquedas y usarse en predicados residuales.

Según el término predicado residual, ese es mi propio término para esta propiedad de una búsqueda. Una combinación de combinación lo llama explícitamente su equivalente a un predicado residual, y la coincidencia de hash lo llama a uno residual de sonda (que puede obtener de TSA si coincide con el hash). Pero en una Búsqueda simplemente lo llaman Predicado, lo que lo hace parecer menos malo de lo que es.

Rob Farley
fuente
3

GetItemToProcessIndex no es totalmente buscable porque su cláusula where está activada ItemState + LastAccessTime + CreationTime. Las columnas indexadas y la cláusula where no coinciden perfectamente.

Si crea un índice de cobertura ItemState + LastAccessTime + CreationTime, por cada coincidencia que obtenga de GetItemToProcessIndex, también obtendrá el valor de su Clave primaria (ItemId). Solo tiene que asegurarse de que la segunda fecha coincida.

Esto es todo lo que necesita para saltar a la ubicación de la fila en su página y actualizarla.

Con su índice actual, puede ayudar al servidor a encontrar filas con el ItemState que desee, pero aún así tendrá que leerlas todas del índice para encontrar las coincidencias correctas en LastAccessTime + CreationTime. Dependiendo de los predicados de fecha y el tamaño del conjunto coincidente y lo que debe excluirse, puede dar lugar a mucho más IO que un índice que cubra perfectamente solo en las 3 columnas que buscaría ItemState y la segunda columna (primera fecha indexada) . Sin embargo, se puede incluir la segunda fecha en el indexado. Las columnas adicionales no deben indexarse ​​entre estos 3, aunque podría estar bien como una cuarta columna (consulte la respuesta de rob sobre columnas adicionales).

Julien Vavasseur
fuente