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:
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.
Eu tentei isso em 2005SP3 e 2008R2 até agora, mesmos resultados em ambos. Alguma ideia do que pode causar isso?
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:
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 :
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:
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':
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:
É no próximo operador paralelo que a causa e a explicação possivelmente aparecem.
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 .
1,004,588
é uma figura que surge muito em meus testes também.Também vejo isso no plano um pouco mais simples abaixo.
Outras figuras de interesse no plano de execução são
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
TOP
especificaçã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,588
contagem 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.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).
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.