Ich denke, ich habe eine teilweise Erklärung dafür, aber bitte zögern Sie nicht, es abzuschießen oder Alternativen zu posten. @MartinSmith hat definitiv etwas vor, indem es den Effekt von TOP im Ausführungsplan hervorhebt.
Einfach ausgedrückt, ist 'Actual Row Count' nicht die Anzahl der Zeilen, die ein Operator verarbeitet, sondern die Häufigkeit, mit der die GetNext () -Methode des Operators aufgerufen wird.
Entnommen aus BOL :
Die physischen Operatoren initialisieren, erfassen Daten und schließen sie. Insbesondere kann der physische Operator die folgenden drei Methodenaufrufe beantworten:
- Init (): Die Init () -Methode veranlasst einen physischen Operator, sich selbst zu initialisieren und alle erforderlichen Datenstrukturen einzurichten. Der physische Operator empfängt möglicherweise viele Init () - Aufrufe, obwohl ein physischer Operator normalerweise nur einen empfängt.
- GetNext (): Die GetNext () -Methode veranlasst einen physischen Operator, die erste oder nachfolgende Datenzeile abzurufen. Der physische Operator kann null oder viele GetNext () - Aufrufe empfangen.
- Close (): Die Close () -Methode veranlasst einen physischen Operator, einige Bereinigungsvorgänge durchzuführen und sich selbst herunterzufahren. Ein physischer Operator erhält nur einen Close () -Aufruf.
Die GetNext () -Methode gibt eine Datenzeile zurück und die Häufigkeit, mit der sie aufgerufen wird, wird in der Showplan-Ausgabe, die mit SET STATISTICS PROFILE ON oder SET STATISTICS XML ON erstellt wird, als ActualRows angezeigt.
Der Vollständigkeit halber sind einige Hintergrundinformationen zu den Paralleloperatoren hilfreich. Die Arbeit wird in einem parallelen Plan durch den Neupartitions-Stream oder die Verteilungs-Stream-Operatoren auf mehrere Streams verteilt. Diese verteilen Zeilen oder Seiten zwischen Threads mithilfe eines von vier Mechanismen:
- Hash verteilt Zeilen basierend auf einem Hash der Spalten in der Zeile
- Round-Robin verteilt Zeilen, indem die Liste der Threads in einer Schleife durchlaufen wird
- Broadcast verteilt alle Seiten oder Zeilen auf alle Threads
- Die Bedarfspartitionierung wird nur für Scans verwendet. Die Themen drehen sich, fordern eine Seite mit Daten vom Bediener an, verarbeiten sie und fordern eine weitere Seite an, wenn sie fertig sind.
Der erste Verteilungsstrom-Operator (ganz rechts im Plan) verwendet die Bedarfsaufteilung für die Zeilen, die von einem konstanten Scan stammen. Es gibt drei Threads, die GetNext () 6, 4 und 0-mal für insgesamt 10 "Aktuelle Zeilen" aufrufen:
<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>
Beim nächsten Verteilungsoperator haben wir wieder drei Threads, diesmal mit 50, 50 und 0 Aufrufen von GetNext () für insgesamt 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>
Beim nächsten Paralleloperator erscheinen möglicherweise die Ursache und die Erklärung.
<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>
Daher haben wir jetzt 11 Aufrufe von GetNext (), bei denen wir erwartet hatten, 10 zu sehen.
Bearbeiten: 2011-11-13
An diesem Punkt festgefahren , habe ich nach Antworten mit den Kerlen im Clustered-Index gesucht und @MikeWalsh hat freundlicherweise @SQLKiwi hierher geleitet .