El índice busca mucho más lento con la condición OR en comparación con los SELECT separados

8

Sobre la base de estas preguntas y las respuestas dadas:

SQL 2008 Server: pérdida de rendimiento posiblemente conectada con una tabla muy grande

La tabla grande con datos históricos asigna demasiado de SQL Server 2008 Std. memoria: pérdida de rendimiento para otras bases de datos

Tengo una tabla en una base de datos SupervisionP definida así:

CREATE TABLE [dbo].[PenData](
    [IDUkazatel] [smallint] NOT NULL,
    [Cas] [datetime2](0) NOT NULL,
    [Hodnota] [real] NULL,
    [HodnotaMax] [real] NULL,
    [HodnotaMin] [real] NULL,
 CONSTRAINT [PK_Data] PRIMARY KEY CLUSTERED 
(
    [IDUkazatel] ASC,
    [Cas] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[PenData]  WITH NOCHECK ADD  CONSTRAINT [FK_Data_Ukazatel] FOREIGN KEY([IDUkazatel])
REFERENCES [dbo].[Ukazatel] ([IDUkazatel])

ALTER TABLE [dbo].[PenData] CHECK CONSTRAINT [FK_Data_Ukazatel]

Contiene aproximadamente 211 millones de filas.

Ejecuto la siguiente declaración:

DECLARE @t1 DATETIME;
DECLARE @t2 DATETIME;

SET @t1 = GETDATE();
SELECT min(cas) from PenData p WHERE IDUkazatel=24
SELECT min(cas) from PenData p WHERE IDUkazatel=25
SET @t2 = GETDATE();
SELECT DATEDIFF(millisecond,@t1,@t2) AS elapsed_ms;


SET @t1 = GETDATE();
SELECT min(cas) from PenData p WHERE IDUkazatel=24 OR IDUkazatel=25 
SET @t2 = GETDATE();
SELECT DATEDIFF(millisecond,@t1,@t2) AS elapsed_ms;

El resultado se muestra aquí:

Plan de ejecución

El tercer SELECT también carga muchos más datos en la memoria caché de SQL Server.

¿Por qué el tercer SELECT es mucho más lento (8.5 s) que los dos primeros SELECT (16 ms)? ¿Cómo puedo mejorar el rendimiento de la tercera selección con OR? Quiero ejecutar el siguiente comando SQL, pero me parece que crear cursor y ejecutar consultas separadas es mucho más rápido que una sola selección en este caso.

 SELECT MIN(cas) from PenData p WHERE IDUkazatel IN (SELECT IDUkazatel FROM  ...)

EDITAR

Como David sugirió, he estado sobre la flecha gorda:

FatArrow

Vojtěch Dohnal
fuente

Respuestas:

11

Para las dos primeras consultas, todo lo que tiene que hacer es escanear en el índice agrupado hasta la primera entrada para ese valor de IDUkazatel- debido al orden del índice esa fila será el valor más bajo para cas para ese valor de IDUkazatel.

En la segunda consulta, esta optimización no es un valor y probablemente esté buscando la primera fila para IDUkazatel=24luego escanear el índice hasta la última fila con IDUkazatel=25para encontrar el valor mínimo de castodas esas filas.

Si pasa el cursor sobre esa flecha gruesa, verá que está leyendo muchas filas (ciertamente todas para 24, probablemente todas para 25 también), mientras que las flechas delgadas en la salida del plan para los otros dos muestran la topacción que hace que solo Considere una fila.

Puede intentar ejecutar cada consulta y luego obtener el mínimo para los mínimos encontrados:

SELECT MIN(cas)
FROM   (
        SELECT cas=MIN(cas) FROM PenData p WHERE p.IDUkazatel = 24
        UNION ALL
        SELECT cas=MIN(cas) FROM PenData p WHERE p.IDUkazatel = 25
    ) AS minimums

Dicho esto, parece que tiene una tabla con IDUkazatelvalores en lugar de una ORcláusula explícita . El código a continuación funcionará con esa disposición, simplemente reemplace el nombre de la tabla @Tcon el nombre de la tabla que contiene los IDUkazatelvalores:

SELECT 
    MinCas = MIN(CA.PartialMinimum)
FROM @T AS T
CROSS APPLY 
(
    SELECT 
        PartialMinimum = MIN(PD.Cas)
    FROM dbo.PenData AS PD
    WHERE 
        PD.IDUkazatel = T.IDUkazatel
) AS CA;

En un mundo ideal, el optimizador de consultas de SQL Server realizaría esta reescritura por usted, pero no siempre considera esta opción hoy.

David Spillett
fuente
Puede reescribir la última sin la tabla derivada SELECT TOP (1) min_cas=MIN(CAS) ... ORDER BY min_cas;(pero supongo que el plan será el mismo que el suyo).
ypercubeᵀᴹ