Je pense que j'ai peut-être une explication partielle à cela, mais n'hésitez pas à l'abattre ou à publier des alternatives. @MartinSmith est définitivement sur quelque chose en mettant en évidence l'effet de TOP dans le plan d'exécution.
En termes simples, le «nombre réel de lignes» n'est pas un nombre de lignes qu'un opérateur traite, c'est le nombre de fois que la méthode GetNext () de l'opérateur est appelée.
Extrait de BOL :
Les opérateurs physiques initialisent, collectent des données et ferment. Plus précisément, l'opérateur physique peut répondre aux trois appels de méthode suivants:
- Init (): La méthode Init () oblige un opérateur physique à s'initialiser et à configurer toutes les structures de données requises. L'opérateur physique peut recevoir de nombreux appels Init (), bien que généralement un opérateur physique n'en reçoive qu'un.
- GetNext (): La méthode GetNext () oblige un opérateur physique à obtenir la première ligne de données, ou la suivante. L'opérateur physique peut recevoir zéro ou plusieurs appels GetNext ().
- Close (): La méthode Close () oblige un opérateur physique à effectuer certaines opérations de nettoyage et à s'arrêter. Un opérateur physique ne reçoit qu'un seul appel Close ().
La méthode GetNext () renvoie une ligne de données et le nombre d'appels apparaît en tant que ActualRows dans la sortie Showplan produite en utilisant SET STATISTICS PROFILE ON ou SET STATISTICS XML ON.
Par souci d'exhaustivité, un petit historique sur les opérateurs parallèles est utile. Le travail est distribué sur plusieurs flux dans un plan parallèle par le flux de répartition ou distribue les opérateurs de flux. Ceux-ci distribuent des lignes ou des pages entre les threads en utilisant l'un des quatre mécanismes:
- Hacher distribue les lignes en fonction d'un hachage des colonnes de la ligne
- Round-robin distribue les lignes en itérant dans la liste des threads dans une boucle
- La diffusion distribue toutes les pages ou lignes à tous les fils
- Le partitionnement à la demande est utilisé uniquement pour les analyses. Les threads tournent, demandent une page de données à l'opérateur, les traitent et demandent une autre page une fois terminé.
Le premier opérateur de flux de distribution (le plus à droite dans le plan) utilise le partitionnement de la demande sur les lignes provenant d'une analyse constante. Il y a trois threads qui appellent GetNext () 6, 4 et 0 fois pour un total de 10 «lignes réelles»:
<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>
Au prochain opérateur de distribution, nous avons à nouveau trois threads, cette fois avec 50, 50 et 0 appels à GetNext () pour un 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>
C'est à l'opérateur parallèle suivant que la cause et l'explication peuvent apparaître.
<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>
Nous avons donc maintenant 11 appels à GetNext (), où nous nous attendions à voir 10.
Modifier: 2011-11-13
Coincé à ce stade, je suis allé chercher des réponses avec les chaps dans l'index clusterisé et @MikeWalsh a gentiment dirigé @SQLKiwi ici .