O índice não está tornando a execução mais rápida e, em alguns casos, está diminuindo a velocidade da consulta. Por que é tão?


34

Eu estava experimentando índices para acelerar as coisas, mas no caso de uma junção, o índice não está melhorando o tempo de execução da consulta e, em alguns casos, está diminuindo a velocidade das coisas.

A consulta para criar tabela de teste e preenchê-la com dados é:

CREATE TABLE [dbo].[IndexTestTable](
    [id] [int] IDENTITY(1,1) PRIMARY KEY,
    [Name] [nvarchar](20) NULL,
    [val1] [bigint] NULL,
    [val2] [bigint] NULL)

DECLARE @counter INT;
SET @counter = 1;

WHILE @counter < 500000
BEGIN
    INSERT INTO IndexTestTable
      (
        -- id -- this column value is auto-generated
        NAME,
        val1,
        val2
      )
    VALUES
      (
        'Name' + CAST((@counter % 100) AS NVARCHAR),
        RAND() * 10000,
        RAND() * 20000
      );

    SET @counter = @counter + 1;
END

-- Index in question
CREATE NONCLUSTERED INDEX [IndexA] ON [dbo].[IndexTestTable]
(
    [Name] ASC
)
INCLUDE (   [id],
    [val1],
    [val2])

Agora, a consulta 1, que foi aprimorada (apenas um pouco, mas a melhoria é consistente) é:

SELECT *
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.ID = I2.ID
WHERE  I1.Name = 'Name1'

Estatísticas e plano de execução sem Índice (neste caso, a tabela usa o índice em cluster padrão):

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 5580, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 109 ms,  elapsed time = 294 ms.

insira a descrição da imagem aqui

Agora com o Índice ativado:

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 2819, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 94 ms,  elapsed time = 231 ms.

insira a descrição da imagem aqui

Agora a consulta fica mais lenta devido ao índice (a consulta não faz sentido, pois foi criada apenas para teste):

SELECT I1.Name,
       SUM(I1.val1),
       SUM(I1.val2),
       MIN(I2.Name),
       SUM(I2.val1),
       SUM(I2.val2)
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.Name = I2.Name
WHERE   
       I2.Name = 'Name1'
GROUP BY
       I1.Name

Com o índice clusterizado ativado:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 4, logical reads 60, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 1, logical reads 155106, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17207 ms,  elapsed time = 17337 ms.

insira a descrição da imagem aqui

Agora com o Índice desativado:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 5, logical reads 8642, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 2, logical reads 165212, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17691 ms,  elapsed time = 9073 ms.

insira a descrição da imagem aqui

As perguntas são:

  1. Mesmo que o índice seja sugerido pelo SQL Server, por que diminui a velocidade por uma diferença significativa?
  2. O que é a junção do Nested Loop que está demorando a maior parte do tempo e como melhorar seu tempo de execução?
  3. Existe algo que estou fazendo de errado ou que perdi?
  4. Com o índice padrão (apenas na chave primária) por que leva menos tempo e com o índice não clusterizado presente, para cada linha na tabela de junção, a linha da tabela unida deve ser encontrada mais rapidamente, porque a junção está na coluna Nome na qual o índice foi criado. Isso é refletido no plano de execução da consulta e o custo da busca por índice é menor quando o IndexA está ativo, mas por que ainda mais lento? Além disso, o que está no loop aninhado à esquerda da junção externa que está causando a desaceleração?

Usando o SQL Server 2012

Respostas:


23

Mesmo que o índice seja sugerido pelo SQL Server, por que diminui a velocidade por uma diferença significativa?

As sugestões de índice são feitas pelo otimizador de consulta. Se encontrar uma seleção lógica de uma tabela que não é bem servida por um índice existente, poderá adicionar uma sugestão de "índice ausente" à saída. Essas sugestões são oportunistas; eles não são baseados em uma análise completa da consulta e não levam em consideração considerações mais amplas. Na melhor das hipóteses, são uma indicação de que uma indexação mais útil pode ser possível, e um DBA qualificado deve dar uma olhada.

A outra coisa a dizer sobre a falta de sugestões de índices é que elas se baseiam no modelo de custo do otimizador e o otimizador estima em quanto o índice sugerido pode reduzir o custo estimado da consulta. As palavras-chave aqui são "modelo" e "estimativas". O otimizador de consulta sabe pouco sobre a configuração do hardware ou outras opções de configuração do sistema - seu modelo é amplamente baseado em números fixos que produzem resultados razoáveis ​​para a maioria das pessoas na maioria dos sistemas na maioria das vezes. Além dos problemas com os números de custo exatos usados, os resultados são sempre estimativas - e as estimativas podem estar erradas.

O que é a junção do Nested Loop que está demorando a maior parte do tempo e como melhorar seu tempo de execução?

Há pouco a ser feito para melhorar o desempenho da operação de junção cruzada; loops aninhados é a única implementação física possível para uma junção cruzada. O carretel de mesa no lado interno da união é uma otimização para evitar redigitalizar o lado interno de cada linha externa. Se essa é uma otimização de desempenho útil depende de vários fatores, mas nos meus testes a consulta é melhor sem ela. Novamente, isso é uma conseqüência do uso de um modelo de custo - meu sistema de CPU e memória provavelmente tem características de desempenho diferentes das suas. Não há dica de consulta específica para evitar o spool da tabela, mas há um sinalizador de rastreamento não documentado (8690) que você pode usar para testar o desempenho da execução com e sem o spool. Se esse fosse um problema real do sistema de produção, o plano sem o carretel pode ser forçado usando um guia de plano baseado no plano produzido com o TF 8690 ativado. O uso de sinalizadores de rastreamento não documentados na produção não é recomendado porque a instalação fica tecnicamente sem suporte e os sinalizadores de rastreamento podem ter efeitos colaterais indesejáveis.

Existe algo que estou fazendo de errado ou que perdi?

O principal que falta é que, embora o plano que utiliza o índice não clusterizado tenha um custo estimado mais baixo, de acordo com o modelo do otimizador, ele tenha um problema significativo no tempo de execução. Se você observar a distribuição de linhas entre os segmentos no plano usando o Índice de Cluster, provavelmente verá uma distribuição razoavelmente boa:

Plano de digitalização

No plano que utiliza a busca de índice não clusterizado, o trabalho acaba sendo executado inteiramente por um thread:

Buscar plano

Isso é uma consequência da maneira como o trabalho é distribuído entre os threads por operações de varredura / busca paralelas. Nem sempre é o caso de uma varredura paralela distribuir o trabalho melhor do que uma pesquisa de índice - mas ocorre nesse caso. Planos mais complexos podem incluir reparticionar trocas para redistribuir o trabalho entre threads. Esse plano não possui essas trocas; portanto, depois que as linhas são atribuídas a um encadeamento, todo o trabalho relacionado é executado no mesmo encadeamento. Se você observar a distribuição do trabalho para os outros operadores no plano de execução, verá que todo o trabalho é realizado pelo mesmo encadeamento, como mostrado para a busca do índice.

Não há dicas de consulta para afetar a distribuição de linhas entre os threads, o importante é estar ciente da possibilidade e poder ler detalhes suficientes no plano de execução para determinar quando está causando um problema.

Com o índice padrão (apenas na chave primária) por que leva menos tempo e com o índice não clusterizado presente, para cada linha na tabela de junção, a linha da tabela unida deve ser encontrada mais rapidamente, porque a junção está na coluna Nome na qual o índice foi criado. Isso é refletido no plano de execução da consulta e o custo da busca por índice é menor quando o IndexA está ativo, mas por que ainda mais lento? Além disso, o que está no loop aninhado à esquerda da junção externa que está causando a desaceleração?

Agora deve ficar claro que o plano de índice não clusterizado é potencialmente mais eficiente, como seria de esperar; é apenas uma má distribuição do trabalho entre os threads no momento da execução, responsável pelo problema de desempenho.

Para completar o exemplo e ilustrar algumas das coisas que mencionei, uma maneira de obter uma melhor distribuição de trabalho é usar uma tabela temporária para conduzir a execução paralela:

SELECT
    val1,
    val2
INTO #Temp
FROM dbo.IndexTestTable AS ITT
WHERE Name = N'Name1';

SELECT 
    N'Name1',
    SUM(T.val1),
    SUM(T.val2),
    MIN(I2.Name),
    SUM(I2.val1),
    SUM(I2.val2)
FROM   #Temp AS T
CROSS JOIN IndexTestTable I2
WHERE
    I2.Name = 'Name1'
OPTION (FORCE ORDER, QUERYTRACEON 8690);

DROP TABLE #Temp;

Isso resulta em um plano que usa as buscas de índice mais eficientes, não apresenta um spool de tabela e distribui bem o trabalho entre os threads:

Plano ideal

No meu sistema, esse plano é executado significativamente mais rápido que a versão Clustered Index Scan.

Se você estiver interessado em aprender mais sobre os aspectos internos da execução de consultas paralelas, assista à minha gravação da sessão do PASS Summit 2013 .


0

Não é realmente uma questão de índice, é mais uma consulta mal escrita. Você tem apenas 100 valores exclusivos de nome, isso deixa uma contagem exclusiva de 5000 por nome.

Portanto, para cada linha da tabela 1 você está juntando 5000 da tabela 2. Você pode dizer 25020004 linhas.

Tente isso, observe que esse é apenas um índice, o que você listou.

    DECLARE @Distincts INT
    SET @Distincts = (SELECT  TOP 1 COUNT(*) FROM IndexTestTable I1 WHERE I1.Name = 'Name1' GROUP BY I1.Name)
    SELECT I1.Name
    , @Distincts
    , SUM(I1.val1) * @Distincts
    , SUM(I1.val2) * @Distincts
    , MIN(I2.Name)
    , SUM(I2.val1)
    , SUM(I2.val2)
    FROM   IndexTestTable I1
    LEFT OUTER JOIN

    (
        SELECT I2.Name
        , SUM(I2.val1) val1
        , SUM(I2.val2) val2
        FROM IndexTestTable I2
        GROUP BY I2.Name
    ) I2 ON  I1.Name = I2.Name
    WHERE I1.Name = 'Name1'
    GROUP BY  I1.Name

E tempo:

    SQL Server parse and compile time: 
       CPU time = 0 ms, elapsed time = 8 ms.
    Table 'IndexTestTable'. Scan count 1, logical reads 31, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 1 ms.

    (1 row(s) affected)
    Table 'IndexTestTable'. Scan count 2, logical reads 62, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 16 ms,  elapsed time = 10 ms.

insira a descrição da imagem aqui

Você não pode culpar índices SQL por consultas mal formadas


11
Obrigado pela resposta, e sim, a consulta pode ser aprimorada, mas a lógica da minha pergunta era que, com o índice padrão (apenas na chave primária), por que demora menos tempo e com o índice não clusterizado presente, para cada linha em Na tabela de junção, a linha da tabela de junção deve ser encontrada mais rapidamente, o que é refletido no plano de execução da consulta e o custo da busca por índice é menor quando o IndexA está ativo, mas por que ainda mais lento? Além disso, o que está no loop aninhado à esquerda da junção externa que está causando a desaceleração? Eu editei a pergunta para adicionar este comentário, para tornar a questão mais clara.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.