Sie verwenden eine neuere Version von SQL Server, sodass der aktuelle Plan viele Informationen enthält. Siehe das Warnschild am SELECT
Bediener? Dies bedeutet, dass SQL Server eine Warnung generiert hat, die die Abfrageleistung beeinträchtigen kann. Sie sollten sich immer diese ansehen:
<Warnings>
<PlanAffectingConvert ConvertIssue="Seek Plan" Expression="[s].[logger_uuid]=CONVERT_IMPLICIT(nchar(17),[d].[uuid],0)" />
<PlanAffectingConvert ConvertIssue="Seek Plan" Expression="CONVERT_IMPLICIT(nvarchar(100),[d].[name],0)=[g].[name]" />
</Warnings>
Es gibt zwei Datentypkonvertierungen, die durch Ihr Schema verursacht werden. Aufgrund der Warnungen vermute ich, dass der Name tatsächlich ein NVARCHAR(100)
und ein logger_uuid
ist NCHAR(17)
. Das in der Frage angegebene Tabellenschema ist möglicherweise nicht korrekt. Sie sollten die Hauptursache für diese Konvertierungen verstehen und beheben. Einige Arten von Datentypkonvertierungen verhindern Indexsuchen, führen zu Problemen bei der Kardinalitätsschätzung und verursachen andere Probleme.
Eine weitere wichtige Sache, die überprüft werden muss, sind Wartestatistiken. Sie können diese auch in den Details des SELECT
Bedieners sehen. Hier ist das XML für Ihre Wartestatistiken und die von der Abfrage aufgewendete Zeit:
<WaitStats>
<Wait WaitType="RESOURCE_GOVERNOR_IDLE" WaitTimeMs="49515" WaitCount="3773" />
<Wait WaitType="SOS_SCHEDULER_YIELD" WaitTimeMs="57164" WaitCount="2466" />
</WaitStats>
<QueryTimeStats ElapsedTime="67135" CpuTime="10007" />
Ich bin kein Cloud-Typ, aber es sieht so aus, als ob Ihre Abfrage eine CPU nicht vollständig aktivieren kann . Dies hängt wahrscheinlich mit Ihrer aktuellen Azure-Ebene zusammen. Die Abfrage benötigte bei der Ausführung nur etwa 10 Sekunden CPU, dauerte jedoch 67 Sekunden. Ich glaube, dass 50 Sekunden dieser Zeit damit verbracht wurden, gedrosselt zu werden, und 7 Sekunden dieser Zeit wurden Ihnen gegeben, aber für andere Abfragen verwendet, die gleichzeitig ausgeführt wurden. Die schlechte Nachricht ist, dass die Abfrage langsamer ist, als es aufgrund Ihrer Stufe sein könnte. Die gute Nachricht ist, dass eine Reduzierung der CPU zu einer 5-fachen Reduzierung der Laufzeit führen kann. Mit anderen Worten, wenn Sie die Abfrage dazu bringen können, 1 Sekunde CPU zu verwenden, wird möglicherweise eine Laufzeit von etwa 5 Sekunden angezeigt.
Als Nächstes können Sie die Eigenschaft Aktuelle Zeitstatistik in Ihren Bedienerdetails überprüfen, um festzustellen, wo die CPU-Zeit verbracht wurde. Ihr Plan verwendet den Zeilenmodus, sodass die CPU-Zeit für einen Operator die Summe der Zeit ist, die dieser Operator sowie seine untergeordneten Elemente verbringen. Dies ist ein relativ einfacher Plan, sodass es nicht lange dauert, festzustellen, dass der Clustered-Index-Scan logger_data
6527 ms CPU-Zeit benötigt. Der Loop-Join, der ihn aufruft, benötigt 10006 ms CPU-Zeit, sodass die gesamte CPU Ihrer Abfrage in diesem Schritt verbraucht wird. Ein weiterer Hinweis darauf, dass bei diesem Schritt etwas schief geht, finden Sie in der Dicke der relativen Pfeile:
Von diesem Operator werden viele Zeilen zurückgegeben, daher lohnt es sich, sich die Details anzusehen. Wenn Sie sich die tatsächliche Anzahl der Zeilen für den Clustered-Index-Scan ansehen, sehen Sie, dass 14088885 Zeilen zurückgegeben und 14100798 Zeilen gelesen wurden. Die Kardinalität der Tabelle beträgt jedoch nur 484803 Zeilen. Intuitiv scheint das ziemlich ineffizient zu sein, oder? Der Clustered-Index-Scan gibt weit mehr als die Anzahl der Zeilen in der Tabelle zurück. Ein anderer Plan mit einem anderen Join-Typ oder einer anderen Zugriffsmethode in der Tabelle ist wahrscheinlich effizienter.
Warum hat SQL Server so viele Zeilen gelesen und zurückgegeben? Der Clustered-Index befindet sich auf der Innenseite einer verschachtelten Schleife. Es gibt 38 Zeilen, die von der Außenseite der Schleife zurückgegeben werden (der Scan in der logger
Tabelle), sodass der Scan bei logger_data
38 Mal ausgeführt wird. 484803 * 38 = 18422514, was ziemlich nahe an der Anzahl der gelesenen Zeilen liegt. Warum hat SQL Server einen solchen Plan gewählt, der sich so ineffizient anfühlt? Es wird sogar geschätzt, dass 57 Scans der Tabelle durchgeführt werden. Der Plan, den Sie erhalten haben, war also wahrscheinlich effizienter als vermutet.
Sie haben sich vielleicht gefragt, warum TOP
Ihr Plan einen Operator enthält. SQL Server eingeführt , um eine Reihe Ziel , wenn eine Abfrage - Plan für Ihre Abfrage zu erstellen. Dies ist möglicherweise detaillierter als gewünscht. In der Kurzversion muss SQL Server jedoch nicht immer alle Zeilen eines Clustered-Index-Scans zurückgeben. Manchmal kann es vorzeitig beendet werden, wenn nur eine feste Anzahl von Zeilen benötigt wird und diese Zeilen gefunden werden, bevor das Ende des Scans erreicht ist. Ein Scan ist nicht so teuer, wenn er vorzeitig beendet werden kann, sodass die Bedienerkosten durch eine Formel abgezinst werden, wenn ein Zeilenziel vorliegt. Mit anderen Worten, SQL Server erwartet, den Clustered-Index 57 Mal zu scannen, geht jedoch davon aus, dass die benötigte einzelne Zeile sehr schnell gefunden wird. Aufgrund des Vorhandenseins von benötigt es nur eine einzige Zeile von jedem ScanTOP
Operator.
Sie können Ihre Abfrage beschleunigen, indem Sie das Abfrageoptimierungsprogramm dazu ermutigen, einen Plan auszuwählen, der die logger_data
Tabelle nicht 38 Mal scannt . Dies kann so einfach sein wie das Eliminieren der Datentypkonvertierungen. Dadurch könnte SQL Server eine Indexsuche anstelle eines Scans durchführen. Wenn nicht, korrigieren Sie die Conversions und erstellen Sie einen Deckungsindex für logger_data
:
CREATE INDEX IX ON logger_data (category_name, logger_uuid);
Das Abfrageoptimierungsprogramm wählt einen Plan basierend auf den Kosten aus. Durch Hinzufügen dieses Index ist es unwahrscheinlich, dass der langsame Plan erstellt wird, der viele Scans für logger_data ausführt, da der Zugriff auf die Tabelle über eine Indexsuche anstelle eines Clustered-Index-Scans billiger ist.
Wenn Sie den Index nicht hinzufügen können, können Sie einen Abfragehinweis hinzufügen, um die Einführung von Zeilenzielen zu deaktivieren : USE HINT('DISABLE_OPTIMIZER_ROWGOAL'))
. Sie sollten dies nur tun, wenn Sie sich mit dem Konzept der Reihenziele wohl fühlen und diese verstehen. Das Hinzufügen dieses Hinweises sollte zu einem anderen Plan führen, aber ich kann nicht sagen, wie effizient er sein wird.