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 / 174837
Accepted
Martin Smith
Martin Smith
Asked: 2017-05-30 07:06:02 +0800 CST2017-05-30 07:06:02 +0800 CST 2017-05-30 07:06:02 +0800 CST

Por que o SQL Server estima que menos linhas serão emitidas de uma junção após a inserção de algumas linhas?

  • 772

O abaixo é uma versão simplificada de algo que encontrei na produção (onde o plano ficou catastroficamente pior em um dia em que um número excepcionalmente alto de lotes foi processado).

A reprodução foi testada em 2014 e 2016 com o novo estimador de cardinalidade.

CREATE TABLE T1 (FromDate  DATE, ToDate DATE, SomeId INT, BatchNumber INT);

INSERT INTO T1
SELECT TOP 1000 FromDate = '2017-01-01',
                ToDate = '2017-01-01',
                SomeId = ROW_NUMBER() OVER (ORDER BY @@SPID) -1,
                BatchNumber = 1
FROM   master..spt_values v1

CREATE TABLE T2 (SomeDateTime DATETIME, SomeId INT, INDEX IX(SomeDateTime));

INSERT INTO T2
SELECT TOP 1000000 '2017-01-01',
                   ROW_NUMBER() OVER (ORDER BY @@SPID) %1000
FROM   master..spt_values v1,
       master..spt_values v2

T1contém 1.000 linhas.

Os FromDate, ToDate, e BatchNumbersão idênticos em todos eles. O único valor que difere é SomeIdcom valores entre 0e999

+------------+------------+--------+-----------+
|  FromDate  |   ToDate   | SomeId | BatchNumber |
+------------+------------+--------+-----------+
| 2017-01-01 | 2017-01-01 |      0 |         1 |
| 2017-01-01 | 2017-01-01 |      1 |         1 |
....
| 2017-01-01 | 2017-01-01 |    998 |         1 |
| 2017-01-01 | 2017-01-01 |    999 |         1 |
+------------+------------+--------+-----------+

T2contém 1 milhão de linhas

mas apenas 1.000 distintos. Cada um repetido 1.000 vezes como abaixo.

+-------------------------+--------+-------+
|      SomeDateTime       | SomeId | Count |
+-------------------------+--------+-------+
| 2017-01-01 00:00:00.000 |      0 |  1000 |
| 2017-01-01 00:00:00.000 |      1 |  1000 |
...
| 2017-01-01 00:00:00.000 |    998 |  1000 |
| 2017-01-01 00:00:00.000 |    999 |  1000 |
+-------------------------+--------+-------+

Executando o seguinte

SELECT *
FROM   T1
       INNER JOIN T2
               ON CAST(t2.SomeDateTime AS DATE) BETWEEN T1.FromDate AND T1.ToDate
                  AND T1.SomeId = T2.SomeId
WHERE  T1.BatchNumber = 1

Demora cerca de 7 segundos na minha máquina. As linhas reais e estimadas são perfeitas para todos os operadores no plano.

insira a descrição da imagem aqui

Agora adicione 3.000 lotes adicionais a T1 (com números de lote de 2 a 3001). Cada um deles clona as mil linhas existentes para o lote número 1

INSERT INTO T1
SELECT T1.FromDate,
       T1.ToDate,
       T1.SomeId,
       Nums.NewBatchNumber
FROM   T1
       CROSS JOIN (SELECT TOP (3000) 1 + ROW_NUMBER() OVER (ORDER BY @@SPID) AS NewBatchNumber
                   FROM   master..spt_values v1, master..spt_values v2) Nums 

e atualize as estatísticas para dar sorte

 UPDATE STATISTICS T1 WITH FULLSCAN

E execute a consulta original novamente.

SELECT *
FROM   T1
       INNER JOIN T2
               ON CAST(t2.SomeDateTime AS DATE) BETWEEN T1.FromDate AND T1.ToDate
                  AND T1.SomeId = T2.SomeId
WHERE  T1.BatchNumber = 1

Deixei correr por um minuto antes de matá-lo. Naquela época, ele havia gerado 40.380 linhas, então acho que levaria 25 minutos para produzir o milhão completo.

A única coisa que mudou é que adicionei algumas linhas adicionais que não correspondiam ao T1.BatchNumber = 1predicado.

No entanto, o plano agora mudou. Em vez disso, ele usa loops aninhados e, embora o número de linhas que saem t1ainda seja estimado corretamente em 1.000 (①), a estimativa do número de linhas unidas agora caiu de 1 milhão para mil (②).

insira a descrição da imagem aqui

Então a pergunta é...

Por que adicionar linhas adicionais de BatchNumber <> 1alguma forma afeta as estimativas para linhas unidas quando BatchNumber = 1?

Certamente parece contra-intuitivo que adicionar linhas a uma tabela acabe reduzindo o número estimado de linhas da consulta como um todo.

sql-server sql-server-2014
  • 1 1 respostas
  • 205 Views

1 respostas

  • Voted
  1. Best Answer
    Joe Obbish
    2017-05-30T19:24:33+08:002017-05-30T19:24:33+08:00

    É importante lembrar que não há garantia de consistência conforme você altera as consultas ou os dados nas tabelas. O otimizador de consulta pode alternar para o uso de um método diferente de estimativa de cardinalidade (como o uso de densidade em vez de histogramas), o que pode fazer com que duas consultas pareçam inconsistentes entre si. Com isso dito, certamente parece que o otimizador de consulta está fazendo uma escolha irracional no seu caso, então vamos nos aprofundar.

    Sua demonstração é muito complicada, então vou trabalhar com um exemplo mais simples que, acredito, mostra o mesmo comportamento. Iniciando a preparação de dados e definições de tabela:

    DROP TABLE dbo.T1 IF EXISTS;
    CREATE TABLE dbo.T1 (FromDate DATE, ToDate DATE, SomeId INT);
    
    INSERT INTO dbo.T1 WITH (TABLOCK)
    SELECT TOP 1000 NULL, NULL, 1
    FROM master..spt_values v1;
    
    DROP TABLE dbo.T2 IF EXISTS;
    CREATE TABLE dbo.T2 (SomeDateTime DATETIME, INDEX IX(SomeDateTime));
    
    INSERT INTO dbo.T2 WITH (TABLOCK)
    SELECT TOP 2 NULL
    FROM master..spt_values v1
    CROSS JOIN master..spt_values v2;
    

    Aqui está a SELECTconsulta para investigar:

    SELECT *
    FROM T1
    INNER JOIN T2 ON t2.SomeDateTime BETWEEN T1.FromDate AND T1.ToDate
    WHERE T1.SomeId = 1;
    

    Essa consulta é simples o suficiente para que possamos elaborar a fórmula para a estimativa de cardinalidade sem nenhum sinalizador de rastreamento. No entanto, tentarei usar o TF 2363 conforme for para ilustrar melhor o que está acontecendo dentro do otimizador. Não está claro se terei sucesso.

    Defina as seguintes variáveis:

    C1= número de linhas na tabela T1

    C2= número de linhas na tabela T2

    S1= a seletividade do T1.SomeIdfiltro

    Minha alegação é que a estimativa de cardinalidade para a consulta acima é a seguinte:

    1. Quando >= * :C2S1C1

    C2* com limite inferior de * S1S1C1

    1. Quando < * :C2S1C1

    164.317* * com um limite superior de *C2S1S1C1

    Vamos passar por alguns exemplos, embora eu não vá passar por todos os que testei. Para a preparação de dados inicial, temos:

    C1= 1000

    C2= 2

    S1= 1,0

    Portanto, a estimativa de cardinalidade deve ser:

    2 * 164,317 = 328,634

    A captura de tela impossível de falsificar abaixo prova isso:

    Exemplo 1

    Usando o sinalizador de rastreamento não documentado 2363, podemos obter algumas pistas sobre o que está acontecendo:

    Plan for computation:
    
      CSelCalcColumnInInterval
    
          Column: QCOL: [SE_DB2].[dbo].[T1].SomeId
    
    Loaded histogram for column QCOL: [SE_DB2].[dbo].[T1].SomeId from stats with id 2
    
    Selectivity: 1
    
    Stats collection generated: 
    
      CStCollFilter(ID=3, CARD=1000)
    
          CStCollBaseTable(ID=1, CARD=1000 TBL: T1)
    
    End selectivity computation
    
    Begin selectivity computation
    
    Input tree:
    
    ...
    
    Plan for computation:
    
      CSelCalcSimpleJoinWithUpperBound (Using base cardinality)
    
          CSelCalcOneSided (RIGHT)
    
              CSelCalcCombineFilters_ExponentialBackoff (AND)
    
                  CSelCalcFixedFilter (0.3)
    
                  CSelCalcFixedFilter (0.3)
    
    Selectivity: 0.164317
    
    Stats collection generated: 
    
      CStCollJoin(ID=4, CARD=328.634 x_jtInner)
    
          CStCollFilter(ID=3, CARD=1000)
    
              CStCollBaseTable(ID=1, CARD=1000 TBL: T1)
    
          CStCollBaseTable(ID=2, CARD=2 TBL: T2)
    
    End selectivity computation
    

    Com o novo CE, obtemos a estimativa usual de 16% para um BETWEEN. Isso se deve ao recuo exponencial com o novo 2014 CE. Cada desigualdade tem uma estimativa de cardinalidade de 0,3, então BETWEENé calculada como 0,3 * sqrt(0,3) = 0,164317. Multiplique a seletividade de 16% pelo número de linhas em T2 e T1 e obtemos nossa estimativa. Parece bastante razoável. Vamos aumentar o número de linhas T2para 7. Agora temos o seguinte:

    C1= 1000

    C2= 7

    S1= 1,0

    Portanto, a estimativa de cardinalidade deve ser 1000 porque:

    7 * 164,317 = 1150 > 1000

    O plano de consulta confirma:

    Exemplo 1

    Podemos dar outra olhada no TF 2363, mas parece que a seletividade foi ajustada nos bastidores para respeitar o limite superior. Suspeito que isso CSelCalcSimpleJoinWithUpperBoundimpede que a estimativa de cardinalidade ultrapasse 1000.

    Loaded histogram for column QCOL: [SE_DB2].[dbo].[T1].SomeId from stats with id 2
    
    Selectivity: 1
    
    Stats collection generated: 
    
      CStCollFilter(ID=3, CARD=1000)
    
          CStCollBaseTable(ID=1, CARD=1000 TBL: T1)
    
    End selectivity computation
    
    Begin selectivity computation
    
    Input tree:
    
    ...
    
    Plan for computation:
    
      CSelCalcSimpleJoinWithUpperBound (Using base cardinality)
    
          CSelCalcOneSided (RIGHT)
    
              CSelCalcCombineFilters_ExponentialBackoff (AND)
    
                  CSelCalcFixedFilter (0.3)
    
                  CSelCalcFixedFilter (0.3)
    
    Selectivity: 0.142857
    
    Stats collection generated: 
    
      CStCollJoin(ID=4, CARD=1000 x_jtInner)
    
          CStCollFilter(ID=3, CARD=1000)
    
              CStCollBaseTable(ID=1, CARD=1000 TBL: T1)
    
          CStCollBaseTable(ID=2, CARD=7 TBL: T2)
    

    Vamos aumentar T2para 50.000 linhas. Agora temos:

    C1= 1000

    C2= 50.000

    S1= 1,0

    Portanto, a estimativa de cardinalidade deve ser:

    50.000 * 1,0 = 50.000

    O plano de consulta confirma novamente. É muito mais fácil adivinhar a estimativa depois que você já descobriu a fórmula:

    exemplo 2

    Saída TF:

    Loaded histogram for column QCOL: [SE_DB2].[dbo].[T1].SomeId from stats with id 2
    
    Selectivity: 1
    
    Stats collection generated: 
    
      CStCollFilter(ID=3, CARD=1000)
    
          CStCollBaseTable(ID=1, CARD=1000 TBL: T1)
    
    ...
    
    Plan for computation:
    
      CSelCalcSimpleJoinWithUpperBound (Using base cardinality)
    
          CSelCalcOneSided (RIGHT)
    
              CSelCalcCombineFilters_ExponentialBackoff (AND)
    
                  CSelCalcFixedFilter (0.3)
    
                  CSelCalcFixedFilter (0.3)
    
    Selectivity: 0.001
    
    Stats collection generated: 
    
      CStCollJoin(ID=4, CARD=50000 x_jtInner)
    
          CStCollFilter(ID=3, CARD=1000)
    
              CStCollBaseTable(ID=1, CARD=1000 TBL: T1)
    
          CStCollBaseTable(ID=2, CARD=50000 TBL: T2)
    

    Para este exemplo, a retirada exponencial parece ser irrelevante:

    5000 * 1000 * 0,001 = 50000.

    Agora vamos adicionar 3k linhas a T1 com um SomeIdvalor de 0. Codifique para fazer isso:

    INSERT INTO T1 WITH (TABLOCK)
    SELECT TOP 3000 NULL, NULL, 0
    FROM   master..spt_values v1,
           master..spt_values v2;
    
    UPDATE STATISTICS T1 WITH FULLSCAN;
    

    Agora temos:

    C1= 4000

    C2= 50.000

    S1= 0,25

    Portanto, a estimativa de cardinalidade deve ser:

    50.000 * 0,25 = 12.500

    O plano de consulta confirma:

    exemplo 3

    Este é o mesmo comportamento que você chamou na pergunta. Adicionei linhas irrelevantes a uma tabela e a estimativa de cardinalidade diminuiu. Por que isso aconteceu? Preste atenção nas linhas em negrito:

    Loaded histogram for column QCOL: [SE_DB2].[dbo].[T1].SomeId from stats with id 2
    

    Seletividade: 0,25

    Stats collection generated: 
    
      CStCollFilter(ID=3, CARD=1000)
    
          CStCollBaseTable(ID=1, CARD=4000 TBL: T1)
    
    End selectivity computation
    
    Begin selectivity computation
    
    Input tree:
    
    ...
    
    Plan for computation:
    
      CSelCalcSimpleJoinWithUpperBound (Using base cardinality)
    
          CSelCalcOneSided (RIGHT)
    
              CSelCalcCombineFilters_ExponentialBackoff (AND)
    
                  CSelCalcFixedFilter (0.3)
    
                  CSelCalcFixedFilter (0.3)
    

    Seletividade: 0,00025

    Stats collection generated: 
    
      CStCollJoin(ID=4, CARD=12500 x_jtInner)
    
          CStCollFilter(ID=3, CARD=1000)
    
              CStCollBaseTable(ID=1, CARD=4000 TBL: T1)
    
          CStCollBaseTable(ID=2, CARD=50000 TBL: T2)
    
    End selectivity computation
    

    Parece que a estimativa de cardinalidade para este caso foi calculada como:

    C1* * * / ( * )S1C2S1S1C1

    Or for this particular example:

    4000 * 0.25 * 50000 * 0.25 / (0.25 * 4000) = 12500

    The general formula can of course can be simplified to:

    C2 * S1

    Which is the formula that I claimed above. It seems like there's some cancellation going on that shouldn't be. I would expect the total number of rows in T1 to be relevant to the estimate.

    If we insert more rows into T1 we can see the lower bound in action:

    INSERT INTO T1 WITH (TABLOCK)
    SELECT TOP 997000 NULL, NULL, 0
    FROM   master..spt_values v1,
           master..spt_values v2;
    
    UPDATE STATISTICS T1 WITH FULLSCAN;
    

    The cardinality estimate in this case is 1000 rows. I will omit the query plan and the TF 2363 output.

    Para encerrar, esse comportamento é bastante suspeito, mas não sei o suficiente para declarar se é um bug ou não. Meu exemplo não corresponde exatamente à sua reprodução, mas acredito que observei o mesmo comportamento geral. Também eu diria que você tem um pouco de sorte com a forma como escolheu seus dados iniciais. Parece haver uma quantidade razoável de suposições acontecendo pelo otimizador, então eu não ficaria muito preso ao fato de que a consulta original retornou 1 milhão de linhas que correspondiam exatamente à estimativa.

    • 6

relate perguntas

  • SQL Server - Como as páginas de dados são armazenadas ao usar um índice clusterizado

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

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

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

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