2011-09-20 20 views
14

Estamos viendo una gran diferencia entre estas consultas.SQL por qué es SELECT COUNT (*), MIN (col), MAX (col) más rápido que SELECT MIN (col), MAX (col)

La consulta lenta

SELECT MIN(col) AS Firstdate, MAX(col) AS Lastdate 
FROM table WHERE status = 'OK' AND fk = 4193 

Tabla 'mesa'. Número de escaneo 2, lecturas lógicas 2458969, lecturas físicas 0, lecturas anticipadas 0, lecturas lógicas lob 0, lecturas físicas lob 0, lecturas lecturas lob 0.

Tiempos de ejecución del servidor SQL: Tiempo de CPU = 1966 ms , tiempo transcurrido = 1955 ms.

La consulta rápida

SELECT count(*), MIN(col) AS Firstdate, MAX(col) AS Lastdate 
FROM table WHERE status = 'OK' AND fk = 4193 

Tabla 'mesa'. Número de exploraciones 1, lógica lee 5803, lecturas físicas 0, la lectura anticipada lee 0, vaselina lecturas lógicas 0, física lee 0 vaselina, vaselina lectura anticipada es 0.

servidor de ejecución de SQL tiempos: CPU = 0 ms , tiempo transcurrido = 9 ms.

Pregunta

Cuál es la razón entre la gran diferencia de rendimiento entre las consultas?

actualización Una pequeña actualización basada en preguntas dadas como comentarios:

El orden de ejecución o ejecución repetida cambia nada el rendimiento sabia. No se usan parámetros adicionales y la base de datos (prueba) no está haciendo nada más durante la ejecución.

consulta lenta

|--Nested Loops(Inner Join) 
|--Stream Aggregate(DEFINE:([Expr1003]=MIN([DBTest].[dbo].[table].[startdate]))) 
    | |--Top(TOP EXPRESSION:((1))) 
    |   |--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1008]) WITH ORDERED PREFETCH) 
    |    |--Index Scan(OBJECT:([DBTest].[dbo].[table].[startdate]), ORDERED FORWARD) 
    |    |--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[FK]=(5806) AND [DBTest].[dbo].[table].[status]<>'A') LOOKUP ORDERED FORWARD) 
    |--Stream Aggregate(DEFINE:([Expr1004]=MAX([DBTest].[dbo].[table].[startdate]))) 
     |--Top(TOP EXPRESSION:((1))) 
      |--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1009]) WITH ORDERED PREFETCH) 
        |--Index Scan(OBJECT:([DBTest].[dbo].[table].[startdate]), ORDERED BACKWARD) 
        |--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[FK]=(5806) AND [DBTest].[dbo].[table].[status]<>'A') LOOKUP ORDERED FORWARD) 

consulta rápida

|--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1012],0))) 
    |--Stream Aggregate(DEFINE:([Expr1012]=Count(*), [Expr1004]=MIN([DBTest].[dbo].[table].[startdate]), [Expr1005]=MAX([DBTest].[dbo].[table].[startdate]))) 
     |--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1011]) WITH UNORDERED PREFETCH) 
      |--Index Seek(OBJECT:([DBTest].[dbo].[table].[FK]), SEEK:([DBTest].[dbo].[table].[FK]=(5806)) ORDERED FORWARD) 
      |--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[status]<'A' OR [DBTest].[dbo].[table].[status]>'A') LOOKUP ORDERED FORWARD) 

The execution plan from SSMS

respuesta

La respuesta dada por debajo Martin Smith parece explicar el problema. La versión súper corta es que el analizador de consultas MS-SQL usa erróneamente un plan de consulta en la consulta lenta, lo que causa un escaneo completo de la tabla.

Al agregar un recuento (*), la sugerencia de consulta con (FORCESCAN) o un índice combinado en la fecha de inicio, FK y las columnas de estado corrige el problema de rendimiento.

+2

¿Qué sucede si ejecuta la primera consulta después de la segunda consulta nuevamente? – gbn

+1

¿Quizás porque cuando estás usando un conteo (*) no revisas todos los registros de fk = 4193? – nosbor

+1

¿Estás ejecutando estos uno tras otro? Si es así: ¿qué sucede si coloca 'DBCC DROPCLEANBUFFERS' y' DBCC FREEPROCCACHE' antes de ambas consultas? ¿Qué sucede si cambias la secuencia? ¿Ejecutas primero la consulta rápida y luego la lenta? –

Respuesta

24

El estimador de cardinalidad de SQL Server hace varios supuestos del modelo, como

  • Independencia: las distribuciones de datos en diferentes columnas son independientes a menos que la información de correlación está disponible.
  • Uniformidad: dentro de cada paso del histograma del objeto de estadísticas, los valores distintos se distribuyen uniformemente y cada valor tiene la misma frecuencia.

Source

Hay 810,064 filas de la tabla.

Usted tiene la consulta

SELECT COUNT(*), 
     MIN(startdate) AS Firstdate, 
     MAX(startdate) AS Lastdate 
FROM table 
WHERE status <> 'A' 
     AND fk = 4193 

1.893 (0,23%) filas cumplen el predicado fk = 4193, y de los dos fallan la parte status <> 'A' por lo general 1.891 partido y necesitan ser agregados.

También tiene dos índices que no cubren toda la consulta.

para su búsqueda rápido que utiliza un índice en fk encontrar directamente las filas donde fk = 4193 entonces tiene que hacer 1.893 key lookups encontrar cada fila en el índice agrupado para comprobar el predicado status y recuperar el startdate para la agregación.

Cuando se quita el COUNT(*) de la lista SELECT SQL Server ya no tiene para procesar cada fila de clasificación. Como resultado, considera otra opción.

Usted tiene un índice en startdate por lo que podría iniciar la exploración que desde el principio, hacer búsquedas de claves de vuelta a la tabla base y tan pronto como se encuentra la primera parada fila coincidente, ya que se ha encontrado el MIN(startdate), de manera similar al MAX puede se encuentra con otra exploración comenzando en el otro extremo del índice y trabajando hacia atrás.

El Servidor SQL estima que cada uno de estos escaneos terminará procesando 590 filas antes de que lleguen a uno que coincida con el predicado. Dando 1,180 búsquedas totales vs 1,893 por lo que elige este plan.

La cifra 590 es solo table_size/estimated_number_of_rows_that_match. es decir, el estimador de cardinalidad asume que las filas coincidentes se distribuirán uniformemente por toda la tabla.

Desafortunadamente las 1,891 filas que cumplen con el predicado son no distribuidas al azar con respecto a startdate. De hecho, todos están condensados ​​en un solo segmento de fila 8.205 hacia el final del índice, lo que significa que el escaneo para llegar al MIN(startdate) termina haciendo 801,859 búsquedas de claves antes de que pueda detenerse.

Esto se puede reproducir a continuación.

CREATE TABLE T 
(
id int identity(1,1) primary key, 
startdate datetime, 
fk int, 
[status] char(1), 
Filler char(2000) 
) 

CREATE NONCLUSTERED INDEX ix ON T(startdate) 

INSERT INTO T 
SELECT TOP 810064 Getdate() - 1, 
        4192, 
        'B', 
        '' 
FROM sys.all_columns c1, 
     sys.all_columns c2 


UPDATE T 
SET fk = 4193, startdate = GETDATE() 
WHERE id BETWEEN 801859 and 803748 or id = 810064 

UPDATE T 
SET startdate = GETDATE() + 1 
WHERE id > 810064 


/*Both queries give the same plan. 
UPDATE STATISTICS T WITH FULLSCAN 
makes no difference*/ 

SELECT MIN(startdate) AS Firstdate, 
     MAX(startdate) AS Lastdate 
FROM T 
WHERE status <> 'A' AND fk = 4192 


SELECT MIN(startdate) AS Firstdate, 
     MAX(startdate) AS Lastdate 
FROM T 
WHERE status <> 'A' AND fk = 4193 

Se podría considerar el uso de sugerencias de consulta para forzar el plan para utilizar el índice en lugar de fkstartdate o añadir el índice sugerido que falta resaltado en el plan de ejecución de (fk,status) INCLUDE (startdate) para evitar este problema.

+0

La eliminación de la columna de estado de la consulta ralentiza ambas consultas un par por ciento. – CodingBarfield

+0

Estadísticas de creación automática/Estadísticas de actualización automática es True – CodingBarfield

+0

XML del plan de ejecución http://pastebin.com/mBcgHYkN Estamos comprobando ahora si podría haber sido un plan de mantenimiento que no funciona. – CodingBarfield

Cuestiones relacionadas