O índice procura muito mais devagar com a condição OR em comparação com SELECTs separados

8

Com base nessas perguntas e nas respostas dadas:

SQL 2008 Server - perda de desempenho possivelmente conectada a uma tabela muito grande

Tabela grande com dados históricos aloca muito do SQL Server 2008 Std. memória - perda de desempenho para outros bancos de dados

Eu tenho uma tabela em um banco de dados SupervisionP definido assim:

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]

Ele contém cerca de 211 milhões de linhas.

Eu executo a seguinte declaração:

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;

O resultado é mostrado aqui:

Plano de execução

O terceiro SELECT também carrega muito mais dados no cache de memória do SQL Server.

Por que o terceiro SELECT é muito mais lento (8,5 s) do que os dois primeiros SELECTs (16 ms)? Como posso melhorar o desempenho da terceira seleção com OU? Eu quero executar o seguinte comando SQL, mas parece-me que criar cursor e executar consultas separadas é muito mais rápido do que uma única seleção nesse caso.

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

EDITAR

Como David sugeriu, passei o mouse sobre a flecha gorda:

FatArrow

Vojtěch Dohnal
fonte

Respostas:

11

Nas duas primeiras consultas, tudo o que precisa fazer é varrer no índice clusterizado a primeira entrada para esse valor de IDUkazatel- por causa da ordem do índice em que a linha será o valor mais baixo para cas para esse valor de IDUkazatel.

Na segunda consulta, essa otimização não tem valor e provavelmente está buscando a primeira linha para IDUkazatel=24, em seguida, varrer o índice até a última linha IDUkazatel=25para encontrar o valor mínimo de castodas essas linhas.

Se você passar o mouse sobre essa flecha gorda, verá que ela está lendo muitas linhas (certamente todas para 24, provavelmente também para 25), enquanto as setas finas na saída do plano para as outras duas mostram a topação que causa apenas considere uma linha.

Você pode tentar executar cada consulta e obter o mínimo para os 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

Dito isto, parece que você possui uma tabela com IDUkazatelvalores, e não uma ORcláusula explícita . O código abaixo funcionará com esse arranjo, basta substituir o nome da tabela @Tpelo nome da tabela que contém 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;

Em um mundo ideal, o otimizador de consulta do SQL Server executaria essa reescrita para você, mas nem sempre considera essa opção hoje.

David Spillett
fonte
Você pode reescrever o último sem tabela derivada SELECT TOP (1) min_cas=MIN(CAS) ... ORDER BY min_cas;(mas acho que o plano será o mesmo que o seu).
ypercubeᵀᴹ