Database
 sql >> Datenbank >  >> RDS >> Database

Auswirkung des Ausführungsplans auf ASYNC_NETWORK_IO-Wartezeiten – Teil 1

Vor einigen Wochen wurde auf Twitter unter dem Hashtag #SQLHelp eine interessante Frage zu den Auswirkungen von Ausführungsplänen auf den Wartetyp ASYNC_NETWORK_IO gestellt, die zu einigen unterschiedlichen Meinungen und vielen guten Diskussionen führte.

https://twitter.com/shawndube/status/1225476846537650176

Meine unmittelbare Antwort darauf wäre, dass jemand Ursache und Wirkung falsch interpretiert, da der Wartetyp ASYNC_NETWORK_IO auftritt, wenn die Engine Ergebnisse hat, die sie über TDS an den Client senden kann, aber es gibt keine verfügbaren TDS-Puffer auf der Verbindung, um sie zu senden An. Im Allgemeinen bedeutet dies, dass die Clientseite die Ergebnisse nicht effizient nutzt, aber aufgrund der anschließenden Diskussion war ich neugierig genug, um zu testen, ob ein Ausführungsplan die ASYNC_NETWORK_IO-Wartezeiten tatsächlich erheblich beeinflussen würde oder nicht.

Zusammenfassend:Die Konzentration auf ASYNC_NETWORK_IO Waits allein als Tuning-Metrik ist ein Fehler. Je schneller eine Abfrage ausgeführt wird, desto höher wird dieser Wartetyp wahrscheinlich akkumulieren, selbst wenn der Client die Ergebnisse so schnell wie möglich verarbeitet. (Siehe auch Gregs letzten Beitrag über die Konzentration auf Wartezeiten im Allgemeinen.)

Konfiguration testen

Um die Tests dafür durchzuführen, wurde eine sehr einfache Tabelle basierend auf einem Beispiel generiert, das mir per E-Mail von einem anderen Mitglied der Community zur Verfügung gestellt wurde, das eine Änderung des Wartetyps demonstrierte, aber auch eine völlig andere Abfrage zwischen den beiden hatte Tests mit einer zusätzlichen Tabelle, die im zweiten Test verwendet wurde, und es gab einen Kommentar, um die Ergebnisse auszuschalten, wodurch der wesentliche Teil dieses Wartetyps zunächst entfernt wird, sodass es sich nicht nur um eine Planänderung handelt.

Hinweis:Ich möchte darauf hinweisen, dass dies überhaupt keine negative Aussage gegenüber irgendjemandem ist; Die anschließende Diskussion und weitere Tests, die sich aus der bereitgestellten Originalreproduktion ergaben, waren sehr lehrreich und führten zu weiterer Forschung, um diesen Wartetyp insgesamt zu verstehen. Die ursprüngliche Reproduktion zeigte einen Unterschied, jedoch mit zusätzlichen Änderungen, die nicht Teil der ursprünglich gestellten Frage waren.

DROP TABLE IF EXISTS [DemoTable];
 
CREATE TABLE [DemoTable] (
  ID INT PRIMARY KEY,
  FILLER VARCHAR(100)
);
 
INSERT INTO [DemoTable] WITH (TABLOCK)
SELECT TOP (250000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), REPLICATE('Z', 50)
  FROM master..spt_values t1
  CROSS JOIN master..spt_values t2
  CROSS JOIN master..spt_values t3
  OPTION (MAXDOP 1);
GO

Unter Verwendung dieser Tabelle als Basisdatensatz zum Testen verschiedener Planformen unter Verwendung von Hinweisen wurden die folgenden Abfragen verwendet:

SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER HASH JOIN [DemoTable] t2 ON t1.ID = t2.ID;
 
  SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER MERGE JOIN [DemoTable] t2 ON t1.ID = t2.ID;
 
  SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER LOOP JOIN [DemoTable] t2 ON t1.ID = t2.ID;

Da ich diese Abfragen auf SQL Server 2019 CU1 ausgeführt habe, enthielten die Ausführungspläne die tatsächlichen Wartestatistikinformationen im Zusammenhang mit der Abfrageausführung.

Hinweis: Der Optimierer würde einen Merge Join verwenden, ohne dass die Hinweise auf diesen speziellen Datensatz und diese Abfrage angewendet würden.

Erste Testergebnisse

Für die ersten Tests habe ich einfach SSMS verwendet, um die Abfragen auszuführen, und den tatsächlichen Ausführungsplan gesammelt, um die mit jeder Abfrage verbundenen Warteinformationen zu vergleichen, die unten gezeigt werden. Beachten Sie, dass sich bei dieser Datenmenge die verstrichenen Zeiten nicht wesentlich unterscheiden, ebenso wenig wie die Wartezeiten oder Wartezeiten für ASYNC_NETWORK_IO.

HASH-VERBINDUNG

<WaitStats>
  <Wait WaitType="CXPACKET"         WaitTimeMs="18393" WaitCount="8415" />
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="4394"  WaitCount="6635" />
  <Wait WaitType="HTDELETE"         WaitTimeMs="957"   WaitCount="6"    />
  <Wait WaitType="HTBUILD"          WaitTimeMs="4"     WaitCount="6"    />
  <Wait WaitType="HTREPARTITION"    WaitTimeMs="3"     WaitCount="6"    />
  <Wait WaitType="CMEMTHREAD"       WaitTimeMs="3"     WaitCount="14"   />
  <Wait WaitType="LATCH_EX"         WaitTimeMs="2"     WaitCount="8"    />
</WaitStats>
<QueryTimeStats CpuTime="1068" ElapsedTime="4961" />

VERBINDEN VERBINDEN

<WaitStats>
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="3169" WaitCount="6592" />
</WaitStats>
<QueryTimeStats CpuTime="792" ElapsedTime="3933" />

SCHLEIFENVERBINDUNG

<WaitStats>
  <Wait WaitType="CXPACKET"         WaitTimeMs="13690" WaitCount="8286" />
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="3576"  WaitCount="6631" />
  <Wait WaitType="LATCH_EX"         WaitTimeMs="1"     WaitCount="3"    />
</WaitStats>
<QueryTimeStats CpuTime="2172" ElapsedTime="4084" />

Allerdings wollte ich hier nicht aufhören zu testen, denn meine eigene Erfahrung hat immer wieder gezeigt, dass Management Studio ein sehr ineffizienter Konsument von Ergebnissen aus SQL Server ist und selbst verursachen kann ASYNC_NETWORK_IO wartet auf das Auftreten. Also beschloss ich, die Art und Weise, wie ich Dinge testete, zu ändern und ging zu einer SQLCMD-Ausführung der Abfragen.

Testen mit SQLCMD

Da ich SQLCMD häufig für Demos beim Präsentieren verwende, habe ich eine testscript.sql-Datei mit folgendem Inhalt erstellt:

PRINT 'Minimize Screen';
GO
 
WAITFOR DELAY '00:00:05';
GO
 
SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER HASH JOIN [DemoTable] t2 ON t1.ID = t2.ID;
GO
 
SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER MERGE JOIN [DemoTable] t2 ON t1.ID = t2.ID;
GO
 
SELECT t1.ID, t2.FILLER, t2.FILLER
  FROM [DemoTable] t1
  INNER LOOP JOIN [DemoTable] t2 ON t1.ID = t2.ID;
GO

Dies wurde wie folgt über die Befehlszeile ausgeführt, und während der 5-Sekunden-Verzögerung wurde das Fenster minimiert, damit die Ausführung während der Verarbeitung keine Ergebnisse darstellen und scrollen konnte:

sqlcmd -S.\SQL2019 -i testscript.sql -dAdventureWorks2017

Um die tatsächlichen Ausführungspläne zu erfassen, habe ich eine Sitzung mit erweiterten Ereignissen durchgeführt, in der das Ereignis query_post_execution_showplan erfasst wurde, von dem ich im Nachhinein dachte, dass ich auf SQL Server 2019 stattdessen query_post_execution_plan_profile hätte verwenden sollen, um die schlanke v3-Implementierung der Infrastruktur zur Profilerstellung der Abfrageausführungsstatistik zu verwenden, aber dieses Ereignis gibt die WaitStats- oder QueryTimeStats-Informationen nicht zurück, es sei denn, query_post_execution_showplan ist gleichzeitig auch aktiviert. Da es sich außerdem um eine isolierte Testmaschine ohne andere Arbeitslast handelt, sind die Auswirkungen der Standardprofilerstellung hier nicht wirklich von großer Bedeutung.

CREATE EVENT SESSION [Actual Plan] ON SERVER 
  ADD EVENT sqlserver.query_post_execution_showplan
  (ACTION(sqlserver.session_id));

HASH-VERBINDUNG

<WaitStats>
  <Wait WaitType="CXPACKET"         WaitTimeMs="45722" WaitCount="8674" />
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="11321" WaitCount="6610" />
  <Wait WaitType="HTDELETE"         WaitTimeMs="1174"  WaitCount="6"    />
  <Wait WaitType="HTREPARTITION"    WaitTimeMs="4"     WaitCount="6"    />
  <Wait WaitType="HTBUILD"          WaitTimeMs="3"     WaitCount="5"    />
  <Wait WaitType="LATCH_EX"         WaitTimeMs="2"     WaitCount="7"    />
</WaitStats>
<QueryTimeStats ElapsedTime="11874" CpuTime="1070" />

VERBINDEN VERBINDEN

<WaitStats>
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="10837" WaitCount="6602" />
</WaitStats>
<QueryTimeStats ElapsedTime="11597" CpuTime="789" />

SCHLEIFENVERBINDUNG

<WaitStats>
  <Wait WaitType="CXPACKET"         WaitTimeMs="43587" WaitCount="8620" />
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="11177" WaitCount="6612" />
  <Wait WaitType="LATCH_EX"         WaitTimeMs="1"     WaitCount="3"    />
</WaitStats>
<QueryTimeStats ElapsedTime="11696" CpuTime="2221" />

Dies hat sich tatsächlich nicht als schnellerer Weg zum Ausführen der Abfrage erwiesen, und die Leistung wurde tatsächlich reduziert, indem das Befehlszeilendienstprogramm zum Ausführen der Abfrage verwendet wurde, selbst wenn das Fenster minimiert ist und die Ergebnisse nicht sichtbar gescrollt werden. Bei geöffnetem Fenster betrug die HASH-Ausführungszeit 15708 ms und die ASYNC_NETWORK_IO-Wartezeit 15126 ms. Dies zeigt jedoch, dass sich die Leistung des Clients, der die Ergebnisse verarbeitet, bei denselben genauen Ergebnissen sowohl auf die Wartezeit als auch auf die Ausführungszeit der Abfrage auswirkt.

Auswirkung auf Parallelität?

Eines der Dinge, die mir aufgefallen sind, war, dass nur zwei der Pläne parallel ausgeführt wurden, basierend auf dem Vorhandensein der CXPACKET- und LATCH_EX-Wartezeiten in der Ausführungsplan-XML. Daher habe ich mich gefragt, welche Auswirkungen das Erzwingen eines seriellen Ausführungsplans auf die Ausführung derselben Abfragen mit OPTION (MAXDOP 1) haben würde.

HASH-VERBINDUNG

<WaitStats>
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="4047" WaitCount="6379" />
</WaitStats>
<QueryTimeStats CpuTime="602" ElapsedTime="4619" />

VERBINDEN VERBINDEN

<WaitStats>
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="3699" WaitCount="6608" />
</WaitStats>
<QueryTimeStats CpuTime="810" ElapsedTime="4478" />

SCHLEIFENVERBINDUNG

<WaitStats>
  <Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="2083" WaitCount="5385" />
</WaitStats>
<QueryTimeStats CpuTime="1859" ElapsedTime="3918" />

Beachten Sie hier, dass die Gesamtzahl der Wartezeiten nicht wesentlich zurückgegangen ist. Nur der Join-Plan für serielle Schleifen weist eine wesentliche Änderung in der Anzahl der Wartezeiten oder der damit verbundenen Gesamtwartezeit auf, und isoliert bedeutet dies nicht, dass dies ein positiver Vorteil ist, da die Ausführungszeit für die Abfrage nicht wesentlich verbessert wurde und Es kann andere Faktoren geben, die die Ergebnisse dieses spezifischen Tests beeinflusst haben.

Die folgende Tabelle fasst die ASYNC_NETWORK_IO-Wartezeit und -Anzahl für jeden der Tests zusammen.

PlanType Zeilen WaitCount Wartezeit Ausführungszeit Anwendungsname MAXDOP1 Parallel
Hash 250.000 6.635 4.394 4.961 SSMS Nein J
Zusammenführen 250.000 6.592 3.169 3.933 SSMS Nein Nein
Schleife 250.000 6.631 3.576 4.084 SSMS Nein J
Hash 250.000 6.610 11.321 11.874 SQLCMD Nein J
Zusammenführen 250.000 6.602 10.837 11.597 SQLCMD Nein Nein
Schleife 250.000 6.612 11.177 11.696 SQLCMD Nein J
Hash 250.000 6.379 4.047 4.619 SSMS J Nein
Zusammenführen 250.000 6.608 3.699 4.479 SSMS J Nein
Schleife 250.000 5.385 2.083 3.918 SSMS J Nein

Zusammenfassung

Die Untersuchung dieses Beitrags deckt zwar nicht jeden einzelnen Aspekt von Planänderungen oder des Wartetyps ASYNC_NETWORK_IO ab, zeigt jedoch, dass diese Wartezeit nicht wesentlich von dem Ausführungsplan beeinflusst wird, der für die Ausführung einer Abfrage verwendet wird. Ich würde diesen Wartetyp fast wie den CXPACKET-Wartetyp klassifizieren, wenn ich eine Analyse eines Servers als Ganzes durchführe; normal für die meisten Workloads zu sehen und es sei denn, es ist unglaublich verzerrt und es gibt andere Leistungsprobleme, die auf einen langsamen Verbrauch von Ergebnissen durch Clients hinweisen, wie z die Arbeitsbelastung'.