2009-07-09 19 views
6

Tengo una pregunta sobre los índices de SQL Server. No soy un DBA y supongo que la respuesta es clara para aquellos de ustedes que sí lo son. Estoy utilizando SQL Server 2008.Orden de índice de SQL Server (campo de fecha y hora)

Tengo una tabla que es similar a lo siguiente (pero tiene más columnas):

CREATE TABLE [dbo].[Results](
    [ResultID] [int] IDENTITY(1,1) NOT NULL, 
    [TypeID] [int] NOT NULL, 
    [ItemID] [int] NOT NULL, 
    [QueryTime] [datetime] NOT NULL, 
    [ResultTypeID] [int] NOT NULL, 
    [QueryDay] AS (datepart(day,[querytime])) PERSISTED, 
    [QueryMonth] AS (datepart(month,[querytime])) PERSISTED, 
    [QueryYear] AS (datepart(year,[querytime])) PERSISTED, 
CONSTRAINT [PK_Results] PRIMARY KEY CLUSTERED 
(
    [ResultID] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY] 
) ON [PRIMARY] 

Los campos importantes de aviso aquí están resultid, la clave primaria, y QueryTime la fecha y hora en que se produjo el resultado.

que también tienen el siguiente índice (entre otros):

CREATE NONCLUSTERED INDEX [IDX_ResultDate] ON [dbo].[Results] 
(
    [QueryTime] ASC 
) 
INCLUDE ([ResultID], 
[ItemID], 
[TypeID]) 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, FILLFACTOR = 90) ON [PRIMARY] 

En una base de datos en la que tengo un millón de filas en la tabla, se utiliza el índice cuando se hace una consulta como:

select top 1 * from results where querytime>'2009-05-01' order by ResultID asc 

En otra instancia de la misma base de datos, con 50 millones de filas, SQL Server decide no usar el índice, ya que hace un escaneo de índice en clúster que termina siendo terriblemente lento. (y la velocidad depende de la fecha). Incluso si uso consejos de consulta para hacer que use IDX_ResultDate, todavía es un poco lento y pasa el 94% de su tiempo ordenando por ResultID. Pensé que al crear un índice con ResultID y QueryTime como columnas ordenadas en el índice, podría acelerar mi consulta.

por lo tanto crean los siguientes:

CREATE NONCLUSTERED INDEX [IDX_ResultDate2] ON [dbo].[Results] 
(
[QueryTime] ASC,  
[ResultID] ASC 
) 
INCLUDE ([ItemID], 
[TypeID]) 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, FILLFACTOR = 90) ON [PRIMARY] 
GO 

que supone que utilizaría en primer lugar el tipo de QueryTime para encontrar los resultados coincidentes, que ya se puede ordenar por resultid. Sin embargo, este no es el caso ya que este índice no cambia nada en el rendimiento sobre el existente.

entonces intentado el siguiente índice:

CREATE NONCLUSTERED INDEX [IDX_ResultDate3] ON [dbo].[Results] 
(
    [ResultID] ASC, 
    [QueryTime] ASC 
) 
INCLUDE ([ItemID], 
[TypeID]) 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, FILLFACTOR = 90) ON [PRIMARY] 
GO 

Éste produce el resultado deseado. Parece regresar en tiempo constante (una fracción de segundo).

Sin embargo, estoy desconcertado por qué IDX_ResultDate3 funciona bien, mientras que IDX_ResultDate2 no.

Supongo que una búsqueda binaria en una lista ordenada de QueryTime seguida por la búsqueda en el primer resultado en su lista secundaria de ResultIDs es la forma más rápida de obtener el resultado. (De ahí mi orden de clasificación inicial).

Pregunta adicional: ¿Debo crear una columna persistente con la parte de la fecha de QueryTime e indexar en su lugar (ya tengo tres columnas persistentes como se puede ver arriba)?

Respuesta

12

Supongo que una búsqueda binaria en como lista ordenada de QueryTime siguió al leer el primer resultado en , la lista de elementos secundarios de ResultID es la manera más rápida de obtener el resultado . (De ahí mi orden de clasificación inicial ).

Eso sería rápida en efecto, pero la consulta expresa una petición diferente: Usted está solicitando el resultado con el mínimo resultid de todas las consultas que se produjo después de '2009-05-01'. Para satisfacer la solicitud que debe buscar al comienzo del rango ('2009-05-01'), inicie un escaneo desde esta posición para extraer todo el ResultId, ordénelos y luego regrese el top 1 (el ResultId mínimo). El segundo índice que agregó [idx_ResultDate2] tampoco ayuda mucho. La consulta tiene que hacer más o menos lo mismo buscar y escanear: los ResultIds se ordenan dentro de una fecha de resultado, por lo tanto, para encontrar el ResultId superior de todos los resultados de que quedan después de '2009-05-01' todavía la consulta tiene que escanear el índice hasta el final.

En su último índice, [IDX_ResultDate3], la consulta está haciendo trampa. Lo que hace, inicia un escaneo en el inde y mira el valor de QueryTime, sabiendo que en este escaneo de índice el primer resultado que tiene un QueryTime en el rango deseado (> '2009-05-01') es el que desea (porque se garantiza que el ResultId es el Top 1). Obtienes el resultado en una "fracción de segundo" por pura suerte: tienes un resultado coincidente al comienzo del índice. La consulta puede escanear todo el índice y coincidir con el mismo resultado. Puede insertar un nuevo resultado con un QueryTime como '2010-01-01' y luego buscarlo, verá que el rendimiento se degrada a medida que la consulta tiene que escanear todo el índice hasta el final (aún más rápido que un escaneo de tabla porque del tamaño de índice más estrecho).

Mi pregunta es: ¿está absolutamente seguro de que su consulta debe devolver TOP 1 en ORDEN POR ResultID? ¿O acabas de elegir el pedido de forma arbitraria? Si puede cambiar la solicitud ORDER BY, por ejemplo, QueryTime, cualquiera de los índices (actualizado: con QueryTime como la columna más a la izquierda) devolverá un simple Seek and Fetch, no scansn y ningún ordenamiento.

+0

Muy buena explicación. Entiendo ahora. Veré si puedo rediseñar la aplicación para usar el tipo QueryTime. –

2

Puede cambiar índice agrupado a ([QueryTime], [resultid]), o cambiar la consulta de

select top 1 * from results where querytime>'2009-05-01' order by ResultID asc 

a

select top 1 <only the columns you actually need> from results where querytime>'2009-05-01' order by ResultID asc 

e incluyen todas aquellas columnas en [IDX_ResultDate2]

+1

+1 exactamente - dispare para un índice de "cobertura" que incluya todos los campos necesarios para satisfacer la consulta (si es posible) –

+0

Sí, ya está haciendo eso (no publicado aquí) pero el mismo tipo de rendimiento. –

4

Tiene con un rango de que filtra el estado en un campo junto con ORDER BY en otro campo.

Un índice, incluso un índice compuesto, no se puede utilizar para cumplir ambas condiciones en este caso.

Cuando crea un índice en (queryTime, resultId), el índice se usa para filtrar. El motor aún necesita pedir el resultado.

Cuando crea un índice en (resultId, queryTime), el índice se utiliza para ordenar.

Dado que necesita un resultado TOP 1 y la fila que satisface este resultado pasa a estar en el comienzo del índice, este último enfoque funciona mejor.

Si su condición de filtrado sería selectiva (es decir, devolvería pocas filas), y el primer resultado que necesita estaría en el final del índice, la primera aproximación sería mejor.

Lee este artículo en mi blog para algunos más explicaciones y consejos sobre qué índice de crear en qué condiciones:

+0

Bonita publicación de blog. –

0

La primera cosa que sugeriría es comprobar si las estadísticas para esta tabla (todos los índices) están actualizados.

Dado que obtiene dos planes de ejecución diferentes con diferentes conjuntos de datos, parece que SQL Server está realizando una "llamada de juicio" infame al elegir un plan de ejecución sobre otro.

Estoy de acuerdo con la explicación de Remus de por qué está obteniendo resultados "mágicos" con su último índice.

Su sugerencia también es buena, ¿realmente quieres ordenar por resultID? O si puede ordenar por queryTime, tendrá MUCHO mejor rendimiento porque el plan de ejecución podrá usar el orden de índice como el orden del conjunto de resultados (AND buscará a través del índice, frente al escaneo).

+0

Sí, las estadísticas están actualizadas. (y sí, debe ser ordenado ... ¡por desgracia!) –

0

No estoy seguro de poder responder la pregunta, pero señalaría que la clave del índice agrupado ya está incluida como parte de cualquier otro índice, por lo que es redundante incluir el ResultID como parte de cualquiera de los otros índices que proponga.

Cuestiones relacionadas