AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 117306
Accepted
Geoff Patterson
Geoff Patterson
Asked: 2015-10-08 09:17:13 +0800 CST2015-10-08 09:17:13 +0800 CST 2015-10-08 09:17:13 +0800 CST

Consulta 100x mais lenta no SQL Server 2014, linha de spool de contagem de linhas estima o culpado?

  • 772

Tenho uma query que roda em 800 milissegundos no SQL Server 2012 e leva cerca de 170 segundos no SQL Server 2014 . Acho que reduzi isso a uma estimativa de cardinalidade ruim para o Row Count Spooloperador. Eu li um pouco sobre operadores de spool (por exemplo, here e here ), mas ainda estou tendo problemas para entender algumas coisas:

  • Por que essa consulta precisa de um Row Count Spooloperador? Não acho que seja necessário para correção, então qual otimização específica ele está tentando fornecer?
  • Por que o SQL Server estima que a junção ao Row Count Spooloperador remove todas as linhas?
  • Isso é um bug no SQL Server 2014? Se sim, vou arquivar no Connect. Mas eu gostaria de uma compreensão mais profunda primeiro.

Observação: posso reescrever a consulta como a LEFT JOINou adicionar índices às tabelas para obter um desempenho aceitável no SQL Server 2012 e no SQL Server 2014. Portanto, esta questão é mais sobre entender esta consulta específica e planejar em profundidade e menos sobre como formular a consulta de maneira diferente.


A consulta lenta

Veja este Pastebin para um script de teste completo. Aqui está a consulta de teste específica que estou analisando:

-- Prune any existing customers from the set of potential new customers
-- This query is much slower than expected in SQL Server 2014 
SELECT *
FROM #potentialNewCustomers -- 10K rows
WHERE cust_nbr NOT IN (
    SELECT cust_nbr
    FROM #existingCustomers -- 1MM rows
)


SQL Server 2014: o plano de consulta estimado

O SQL Server acredita que o Left Anti Semi Joinpara o Row Count Spoolfiltrará as 10.000 linhas para 1 linha. Por esse motivo, ele seleciona um LOOP JOINpara a junção subsequente a #existingCustomers.

insira a descrição da imagem aqui


SQL Server 2014: o plano de consulta real

Como esperado (por todos menos o SQL Server!), o Row Count Spoolnão removeu nenhuma linha. Portanto, estamos fazendo um loop de 10.000 vezes quando o SQL Server esperava fazer um loop apenas uma vez.

insira a descrição da imagem aqui


SQL Server 2012: o plano de consulta estimado

Ao usar o SQL Server 2012 (ou OPTION (QUERYTRACEON 9481)no SQL Server 2014), o Row Count Spoolnão reduz o número estimado de linhas e uma junção de hash é escolhida, resultando em um plano muito melhor.

insira a descrição da imagem aqui

A reescrita LEFT JOIN

Para referência, aqui está uma maneira de reescrever a consulta para obter um bom desempenho em todos os SQL Server 2012, 2014 e 2016. No entanto, ainda estou interessado no comportamento específico da consulta acima e se ela é um bug no novo Estimador de cardinalidade do SQL Server 2014.

-- Re-writing with LEFT JOIN yields much better performance in 2012/2014/2016
SELECT n.*
FROM #potentialNewCustomers n
LEFT JOIN (SELECT 1 AS test, cust_nbr FROM #existingCustomers) c
    ON c.cust_nbr = n.cust_nbr
WHERE c.test IS NULL

insira a descrição da imagem aqui

sql-server performance
  • 3 3 respostas
  • 5288 Views

3 respostas

  • Voted
  1. Best Answer
    Martin Smith
    2015-10-09T11:44:31+08:002015-10-09T11:44:31+08:00

    Por que essa consulta precisa de um operador Row Count Spool? ... que otimização específica ele está tentando fornecer?

    A cust_nbrcoluna em #existingCustomersé anulável. Se realmente contiver nulos, a resposta correta aqui é retornar zero linhas ( NOT IN (NULL,...) sempre produzirá um conjunto de resultados vazio).

    Portanto, a consulta pode ser considerada como

    SELECT p.*
    FROM   #potentialNewCustomers p
    WHERE  NOT EXISTS (SELECT *
                       FROM   #existingCustomers e1
                       WHERE  p.cust_nbr = e1.cust_nbr)
           AND NOT EXISTS (SELECT *
                           FROM   #existingCustomers e2
                           WHERE  e2.cust_nbr IS NULL) 
    

    Com o spool de contagem de linhas lá para evitar ter que avaliar o

    EXISTS (SELECT *
            FROM   #existingCustomers e2
            WHERE  e2.cust_nbr IS NULL) 
    

    Mais de uma vez.

    Este parece ser apenas um caso em que uma pequena diferença nas suposições pode fazer uma diferença catastrófica no desempenho.

    Depois de atualizar uma única linha como abaixo...

    UPDATE #existingCustomers
    SET    cust_nbr = NULL
    WHERE  cust_nbr = 1;
    

    ... a consulta foi concluída em menos de um segundo. As contagens de linhas nas versões reais e estimadas do plano agora estão quase corretas.

    SET STATISTICS TIME ON;
    SET STATISTICS IO ON;
    
    SELECT *
    FROM   #potentialNewCustomers
    WHERE  cust_nbr NOT IN (SELECT cust_nbr
                            FROM   #existingCustomers 
                           ) 
    

    insira a descrição da imagem aqui

    Linhas zero são geradas conforme descrito acima.

    Os histogramas de estatísticas e os limites de atualização automática no SQL Server não são granulares o suficiente para detectar esse tipo de alteração de linha única. Indiscutivelmente, se a coluna for anulável, pode ser razoável trabalhar com base no fato de que contém pelo menos um NULL, mesmo que o histograma de estatísticas não indique atualmente que haja algum.

    • 11
  2. Geoff Patterson
    2015-10-14T07:15:37+08:002015-10-14T07:15:37+08:00

    Por que essa consulta precisa de um operador Row Count Spool? Não acho que seja necessário para correção, então qual otimização específica ele está tentando fornecer?

    Veja a resposta completa de Martin para esta pergunta. O ponto-chave é que, se uma única linha dentro de NOT INfor NULL, a lógica booleana funcionará de forma que "a resposta correta seja retornar zero linhas". O Row Count Spooloperador está otimizando essa lógica (necessária).

    Por que o SQL Server estima que a associação ao operador Row Count Spool remove todas as linhas?

    A Microsoft fornece um excelente white paper sobre o SQL 2014 Cardinality Estimator . Neste documento, encontrei as seguintes informações:

    O novo CE assume que os valores consultados existem no conjunto de dados, mesmo que o valor esteja fora do intervalo do histograma. O novo CE neste exemplo usa uma frequência média que é calculada multiplicando a cardinalidade da tabela pela densidade.

    Freqüentemente, essa mudança é muito boa; ele alivia muito o problema da chave ascendente e geralmente produz um plano de consulta mais conservador (estimativa de linha mais alta) para valores que estão fora do intervalo com base no histograma de estatísticas.

    No entanto, neste caso específico, assumir que um NULLvalor será encontrado leva à suposição de que a junção a Row Count Spoolirá filtrar todas as linhas de #potentialNewCustomers. No caso em que de fato existe uma NULLlinha, essa é uma estimativa correta (conforme visto na resposta de Martin). No entanto, no caso de não haver uma NULLlinha, o efeito pode ser devastador porque o SQL Server produz uma estimativa pós-junção de 1 linha, independentemente de quantas linhas de entrada aparecem. Isso pode levar a opções de junção muito ruins no restante do plano de consulta.

    Isso é um bug no SQL 2014? Se sim, vou arquivar no Connect. Mas eu gostaria de uma compreensão mais profunda primeiro.

    Acho que está na área cinzenta entre um bug e uma suposição ou limitação de impacto no desempenho do novo Estimador de cardinalidade do SQL Server. No entanto, essa peculiaridade pode causar regressões substanciais no desempenho em relação ao SQL 2012 no caso específico de uma NOT INcláusula anulável que não possui nenhum NULLvalor.

    Portanto, registrei um problema de conexão para que a equipe SQL esteja ciente das possíveis implicações dessa alteração no Estimador de cardinalidade.

    Atualização: Estamos no CTP3 agora para SQL16, e confirmei que o problema não ocorre lá.

    • 9
  3. Paul White
    2017-09-08T14:03:05+08:002017-09-08T14:03:05+08:00

    A resposta de Martin Smith e sua auto-resposta abordaram todos os pontos principais corretamente, só quero enfatizar uma área para futuros leitores:

    Portanto, esta questão é mais sobre entender essa consulta específica e planejar em profundidade e menos sobre como formular a consulta de maneira diferente.

    O objetivo declarado da consulta é:

    -- Prune any existing customers from the set of potential new customers
    

    Esse requisito é fácil de expressar em SQL, de várias maneiras. Qual deles é escolhido é tanto uma questão de estilo quanto qualquer outra coisa, mas a especificação da consulta ainda deve ser escrita para retornar resultados corretos em todos os casos. Isso inclui a contabilização de nulos.

    Expressando completamente o requisito lógico:

    • Retorne clientes em potencial que ainda não são clientes
    • Liste cada cliente em potencial no máximo uma vez
    • Exclua clientes potenciais e existentes nulos (seja qual for o significado de um cliente nulo)

    Podemos então escrever uma consulta que corresponda a esses requisitos usando qualquer sintaxe que preferirmos. Por exemplo:

    WITH DistinctPotentialNonNullCustomers AS
    (
        SELECT DISTINCT 
            PNC.cust_nbr 
        FROM #potentialNewCustomers AS PNC
        WHERE 
            PNC.cust_nbr IS NOT NULL
    )
    SELECT
        DPNNC.cust_nbr
    FROM DistinctPotentialNonNullCustomers AS DPNNC
    WHERE
        DPNNC.cust_nbr NOT IN
        (
            SELECT 
                EC.cust_nbr 
            FROM #existingCustomers AS EC 
            WHERE 
                EC.cust_nbr IS NOT NULL
        );
    

    Isso produz um plano de execução eficiente, que retorna resultados corretos:

    Plano de execução

    Podemos expressar NOT INcomo <> ALLou NOT = ANYsem afetar o plano ou resultados:

    WITH DistinctPotentialNonNullCustomers AS
    (
        SELECT DISTINCT 
            PNC.cust_nbr 
        FROM #potentialNewCustomers AS PNC
        WHERE 
            PNC.cust_nbr IS NOT NULL
    )
    SELECT
        DPNNC.cust_nbr
    FROM DistinctPotentialNonNullCustomers AS DPNNC
    WHERE
        DPNNC.cust_nbr <> ALL
        (
            SELECT 
                EC.cust_nbr 
            FROM #existingCustomers AS EC 
            WHERE 
                EC.cust_nbr IS NOT NULL
        );
    
    WITH DistinctPotentialNonNullCustomers AS
    (
        SELECT DISTINCT 
            PNC.cust_nbr 
        FROM #potentialNewCustomers AS PNC
        WHERE 
            PNC.cust_nbr IS NOT NULL
    )
    SELECT
        DPNNC.cust_nbr
    FROM DistinctPotentialNonNullCustomers AS DPNNC
    WHERE
        NOT DPNNC.cust_nbr = ANY
        (
            SELECT 
                EC.cust_nbr 
            FROM #existingCustomers AS EC 
            WHERE 
                EC.cust_nbr IS NOT NULL
        );
    

    Ou usando NOT EXISTS:

    WITH DistinctPotentialNonNullCustomers AS
    (
        SELECT DISTINCT 
            PNC.cust_nbr 
        FROM #potentialNewCustomers AS PNC
        WHERE 
            PNC.cust_nbr IS NOT NULL
    )
    SELECT
        DPNNC.cust_nbr
    FROM DistinctPotentialNonNullCustomers AS DPNNC
    WHERE 
        NOT EXISTS
        (
            SELECT * 
            FROM #existingCustomers AS EC
            WHERE
                EC.cust_nbr = DPNNC.cust_nbr
                AND EC.cust_nbr IS NOT NULL
        );
    

    Não há nada de mágico nisso, ou algo particularmente censurável em usar IN, ANY, ou ALL- só precisamos escrever a consulta corretamente, para que ela sempre produza os resultados corretos.

    A forma mais compacta usa EXCEPT:

    SELECT 
        PNC.cust_nbr 
    FROM #potentialNewCustomers AS PNC
    WHERE 
        PNC.cust_nbr IS NOT NULL
    EXCEPT
    SELECT
        EC.cust_nbr 
    FROM #existingCustomers AS EC
    WHERE 
        EC.cust_nbr IS NOT NULL;
    

    Isso também produz resultados corretos, embora o plano de execução possa ser menos eficiente devido à ausência de filtragem de bitmap:

    Non-bitmap execution plan

    A pergunta original é interessante porque expõe um problema que afeta o desempenho com a implementação de verificação nula necessária. O objetivo dessa resposta é que escrever a consulta corretamente também evita o problema.

    • 5

relate perguntas

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Como determinar se um Índice é necessário ou necessário

  • Onde posso encontrar o log lento do mysql?

  • Como posso otimizar um mysqldump de um banco de dados grande?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve