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 / 7233
Accepted
Mark Storey-Smith
Mark Storey-Smith
Asked: 2011-10-26 15:22:42 +0800 CST2011-10-26 15:22:42 +0800 CST 2011-10-26 15:22:42 +0800 CST

Contagens de linhas 'reais' imprecisas no plano paralelo

  • 772

Esta é uma questão puramente acadêmica, na medida em que não está causando nenhum problema e estou apenas interessado em ouvir alguma explicação para o comportamento.

Pegue uma tabela de contagem CTE de junção cruzada padrão de Itzik Ben-Gan:

USE [master]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION [dbo].[TallyTable] 
(   
    @N INT
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN 
(
    WITH 
    E1(N) AS 
    (
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
    )                                       -- 1*10^1 or 10 rows
    , E2(N) AS (SELECT 1 FROM E1 a, E1 b)   -- 1*10^2 or 100 rows
    , E4(N) AS (SELECT 1 FROM E2 a, E2 b)   -- 1*10^4 or 10,000 rows
    , E8(N) AS (SELECT 1 FROM E4 a, E4 b)   -- 1*10^8 or 100,000,000 rows

    SELECT TOP (@N) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS N FROM E8 
)
GO

Emita uma consulta que criará uma tabela de número de linha de 1 milhão:

SELECT
    COUNT(N)
FROM
    dbo.TallyTable(1000000) tt

Dê uma olhada no plano de execução paralela para esta consulta:

Plano de execução paralela

Observe que a contagem de linha 'real' antes do operador de coleta de fluxos é 1.004.588. Após o operador de coleta de fluxos, a contagem de linhas é de 1.000.000 esperados. Mais estranho ainda, o valor não é consistente e varia de execução para execução. O resultado da CONTAGEM é sempre correto.

Emita a consulta novamente, forçando o plano não paralelo:

SELECT
    COUNT(N)
FROM
    dbo.TallyTable(1000000) tt
OPTION (MAXDOP 1)

Desta vez, todos os operadores mostram as contagens de linha 'reais' corretas.

Plano de execução não paralela

Eu tentei isso em 2005SP3 e 2008R2 até agora, mesmos resultados em ambos. Alguma ideia do que pode causar isso?

sql-server
  • 4 4 respostas
  • 1533 Views

4 respostas

  • Voted
  1. Best Answer
    Paul White
    2011-11-14T13:58:26+08:002011-11-14T13:58:26+08:00

    As linhas são passadas pelas trocas internamente do encadeamento do produtor para o consumidor em pacotes (daí CXPACKET - pacote de troca de classe), em vez de uma linha de cada vez. Há uma certa quantidade de buffer dentro da exchange. Além disso, a chamada para encerrar o pipeline do lado do consumidor do Gather Streams deve ser passada em um pacote de controle de volta para os threads do produtor. A programação e outras considerações internas significam que os planos paralelos sempre têm uma certa 'distância de parada'.

    Como consequência, muitas vezes você verá esse tipo de diferença de contagem de linhas em que é realmente necessário menos do que todo o conjunto de linhas potencial de uma subárvore. Nesse caso, o TOP leva a execução a um 'fim antecipado'.

    Mais Informações:

    • Planos de execução paralela são péssimos
    • Compreendendo e usando o paralelismo no SQL Server .
    • 12
  2. Mark Storey-Smith
    2011-10-27T07:42:58+08:002011-10-27T07:42:58+08:00

    Acho que posso ter uma explicação parcial para isso, mas sinta-se à vontade para derrubá-la ou postar qualquer alternativa. @MartinSmith está definitivamente no caminho certo ao destacar o efeito do TOP no plano de execução.

    Simplificando, 'Actual Row Count' não é uma contagem das linhas que um operador processa, é o número de vezes que o método GetNext() do operador é chamado.

    Retirado do BOL :

    Os operadores físicos inicializam, coletam dados e fecham. Especificamente, o operador físico pode responder às três chamadas de método a seguir:

    • Init(): O método Init() faz com que um operador físico se inicialize e configure quaisquer estruturas de dados necessárias. O operador físico pode receber muitas chamadas Init(), embora normalmente um operador físico receba apenas uma.
    • GetNext(): O método GetNext() faz com que um operador físico obtenha a primeira linha de dados ou a subseqüente. O operador físico pode receber zero ou muitas chamadas GetNext().
    • Close(): O método Close() faz com que um operador físico execute algumas operações de limpeza e se desligue. Um operador físico recebe apenas uma chamada Close().

    O método GetNext() retorna uma linha de dados e o número de vezes que ele é chamado aparece como ActualRows na saída Showplan produzida usando SET STATISTICS PROFILE ON ou SET STATISTICS XML ON.

    Para fins de completude, um pouco de conhecimento sobre os operadores paralelos é útil. O trabalho é distribuído para vários fluxos em um plano paralelo pelo fluxo de repartição ou operadores de fluxo de distribuição. Eles distribuem linhas ou páginas entre threads usando um dos quatro mecanismos:

    • Hash distribui linhas com base em um hash das colunas na linha
    • Round-robin distribui linhas iterando pela lista de threads em um loop
    • Broadcast distribui todas as páginas ou linhas para todos os threads
    • O particionamento por demanda é usado apenas para varreduras. Os threads giram, solicitam uma página de dados do operador, processam e solicitam outra página quando terminam.

    O primeiro operador de fluxo de distribuição (mais à direita no plano) usa o particionamento de demanda nas linhas originárias de uma varredura constante. Existem três threads que chamam GetNext() 6, 4 e 0 vezes para um total de 10 'Linhas reais':

    <RunTimeInformation>
           <RunTimeCountersPerThread Thread="2" ActualRows="6" ActualEndOfScans="1" ActualExecutions="1" />
           <RunTimeCountersPerThread Thread="1" ActualRows="4" ActualEndOfScans="1" ActualExecutions="1" />
           <RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
     </RunTimeInformation>
    

    No próximo operador de distribuição, temos três threads novamente, desta vez com 50, 50 e 0 chamadas para GetNext() para um total de 100:

    <RunTimeInformation>
        <RunTimeCountersPerThread Thread="2" ActualRows="50" ActualEndOfScans="1" ActualExecutions="1" />
        <RunTimeCountersPerThread Thread="1" ActualRows="50" ActualEndOfScans="1" ActualExecutions="1" />
        <RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
    </RunTimeInformation>
    

    É no próximo operador paralelo que a causa e a explicação possivelmente aparecem.

    <RunTimeInformation>
        <RunTimeCountersPerThread Thread="2" ActualRows="1" ActualEndOfScans="0" ActualExecutions="1" />
        <RunTimeCountersPerThread Thread="1" ActualRows="10" ActualEndOfScans="0" ActualExecutions="1" />
        <RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
    </RunTimeInformation>
    

    Portanto, agora temos 11 chamadas para GetNext(), onde esperávamos ver 10.

    Editar: 13/11/2011

    Preso neste ponto, procurei respostas com os sujeitos no índice clusterizado e @MikeWalsh gentilmente dirigiu @SQLKiwi aqui .

    • 10
  3. Martin Smith
    2011-10-27T06:39:51+08:002011-10-27T06:39:51+08:00

    1,004,588é uma figura que surge muito em meus testes também.

    Também vejo isso no plano um pouco mais simples abaixo.

    WITH 
    E1(N) AS 
    (
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
    )                                       -- 1*10^1 or 10 rows
    , E2(N) AS (SELECT 1 FROM E1 a, E1 b)   -- 1*10^2 or 100 rows
    , E4(N) AS (SELECT 1 FROM E2 a, E2 b)   -- 1*10^4 or 10,000 rows
    SELECT * INTO #E4 FROM E4;
    
    WITH E8(N) AS (SELECT 1 FROM #E4 a, #E4 b),
    Nums(N) AS (SELECT  TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) FROM E8 )
    SELECT COUNT(N) FROM Nums
    
    DROP TABLE #E4
    

    Plano

    Outras figuras de interesse no plano de execução são

    +----------------------------------+--------------+--------------+-----------------+
    |                                  | Table Scan A | Table Scan B | Row Count Spool |
    +----------------------------------+--------------+--------------+-----------------+
    | Number Of Executions             | 2            |            2 |             101 |
    | Actual Number Of Rows - Total    | 101          |        20000 |         1004588 |
    | Actual Number Of Rows - Thread 0 | -            |              |                 |
    | Actual Number Of Rows - Thread 1 | 95           |        10000 |          945253 |
    | Actual Number Of Rows - Thread 2 | 6            |        10000 |           59335 |
    | Actual Rebinds                   | 0            |            0 |               2 |
    | Actual Rewinds                   | 0            |            0 |              99 |
    +----------------------------------+--------------+--------------+-----------------+
    

    Meu palpite é que, como as tarefas estão sendo processadas em paralelo, uma tarefa está em linhas de processamento no meio do voo quando a outra entrega a milionésima linha ao operador de coleta de fluxos para que linhas adicionais sejam tratadas. Além disso, neste artigo, as linhas são armazenadas em buffer e entregues em lotes para este iterador, portanto, parece bastante provável que o número de linhas processadas exceda em vez de atingir exatamente a TOPespecificação em qualquer evento.

    Editar

    Basta olhar para isso com um pouco mais de detalhes. Percebi que estava obtendo mais variedade do que apenas a 1,004,588contagem de linhas citada acima, então executei a consulta acima em um loop para 1.000 iterações e capturei os planos de execução reais. Descartando os 81 resultados para os quais o grau de paralelismo era zero deu os seguintes números.

    count       Table Scan A: Total Actual Row Spool - Total Actual Rows
    ----------- ------------------------------ ------------------------------
    352         101                            1004588
    323         102                            1004588
    72          101                            1003565
    37          101                            1002542
    35          102                            1003565
    29          101                            1001519
    18          101                            1000496
    13          102                            1002542
    5           9964                           99634323
    5           102                            1001519
    4           9963                           99628185
    3           10000                          100000000
    3           9965                           99642507
    2           9964                           99633300
    2           9966                           99658875
    2           9965                           99641484
    1           9984                           99837989
    1           102                            1000496
    1           9964                           99637392
    1           9968                           99671151
    1           9966                           99656829
    1           9972                           99714117
    1           9963                           99629208
    1           9985                           99847196
    1           9967                           99665013
    1           9965                           99644553
    1           9963                           99623626
    1           9965                           99647622
    1           9966                           99654783
    1           9963                           99625116
    

    Pode-se ver que 1.004.588 foi de longe o resultado mais comum, mas em 3 ocasiões ocorreu o pior caso possível e 100.000.000 de linhas foram processadas. O melhor caso observado foi a contagem de 1.000.496 linhas, que ocorreu 19 vezes.

    O script completo a ser reproduzido está na parte inferior da revisão 2 desta resposta (ele precisará de ajustes se for executado em um sistema com mais de 2 processadores).

    • 7
  4. mrdenny
    2011-10-26T18:20:56+08:002011-10-26T18:20:56+08:00

    Acredito que o problema vem do fato de que vários fluxos podem processar a mesma linha, dependendo de como as linhas são divididas entre os fluxos.

    • 1

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

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

    • 4 respostas
  • Marko Smith

    Como você mostra o SQL em execução em um banco de dados Oracle?

    • 2 respostas
  • Marko Smith

    Como selecionar a primeira linha de cada grupo?

    • 6 respostas
  • Marko Smith

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

    • 10 respostas
  • Marko Smith

    Posso ver Consultas Históricas executadas em um banco de dados SQL Server?

    • 6 respostas
  • Marko Smith

    Como uso currval() no PostgreSQL para obter o último id inserido?

    • 10 respostas
  • Marko Smith

    Como executar o psql no Mac OS X?

    • 11 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
  • Marko Smith

    Passando parâmetros de array para um procedimento armazenado

    • 12 respostas
  • Martin Hope
    Manuel Leduc Restrição exclusiva de várias colunas do PostgreSQL e valores NULL 2011-12-28 01:10:21 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Stuart Blackler Quando uma chave primária deve ser declarada sem cluster? 2011-11-11 13:31:59 +0800 CST
  • Martin Hope
    pedrosanta Listar os privilégios do banco de dados usando o psql 2011-08-04 11:01:21 +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
  • Martin Hope
    BrunoLM Guid vs INT - Qual é melhor como chave primária? 2011-01-05 23:46:34 +0800 CST
  • Martin Hope
    bernd_k Quando devo usar uma restrição exclusiva em vez de um índice exclusivo? 2011-01-05 02:32:27 +0800 CST
  • Martin Hope
    Patrick Como posso otimizar um mysqldump de um banco de dados grande? 2011-01-04 13:13:48 +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