Ich suche Hilfe, um diese Abfrageleistung zu verbessern.
SQL Server 2008 R2 Enterprise , maximaler RAM 16 GB, CPU 40, maximaler Parallelitätsgrad 4.
SELECT DsJobStat.JobName AS JobName
, AJF.ApplGroup AS GroupName
, DsJobStat.JobStatus AS JobStatus
, AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) AS ElapsedSecAVG
, AVG(CAST(DsJobStat.CpuMSec AS FLOAT)) AS CpuMSecAVG
FROM DsJobStat, AJF
WHERE DsJobStat.NumericOrderNo=AJF.OrderNo
AND DsJobStat.Odate=AJF.Odate
AND DsJobStat.JobName NOT IN( SELECT [DsAvg].JobName FROM [DsAvg] )
GROUP BY DsJobStat.JobName
, AJF.ApplGroup
, DsJobStat.JobStatus
HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;
Ausführungsnachricht,
(0 row(s) affected)
Table 'AJF'. Scan count 11, logical reads 45, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsAvg'. Scan count 2, logical reads 1926, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsJobStat'. Scan count 1, logical reads 3831235, physical reads 85, read-ahead reads 3724396, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row(s) affected)
SQL Server Execution Times:
CPU time = 67268 ms, elapsed time = 90206 ms.
Tabellenstruktur:
-- 212271023 rows
CREATE TABLE [dbo].[DsJobStat](
[OrderID] [nvarchar](8) NOT NULL,
[JobNo] [int] NOT NULL,
[Odate] [datetime] NOT NULL,
[TaskType] [nvarchar](255) NULL,
[JobName] [nvarchar](255) NOT NULL,
[StartTime] [datetime] NULL,
[EndTime] [datetime] NULL,
[NodeID] [nvarchar](255) NULL,
[GroupName] [nvarchar](255) NULL,
[CompStat] [int] NULL,
[RerunCounter] [int] NOT NULL,
[JobStatus] [nvarchar](255) NULL,
[CpuMSec] [int] NULL,
[ElapsedSec] [int] NULL,
[StatusReason] [nvarchar](255) NULL,
[NumericOrderNo] [int] NULL,
CONSTRAINT [PK_DsJobStat] PRIMARY KEY CLUSTERED
( [OrderID] ASC,
[JobNo] ASC,
[Odate] ASC,
[JobName] ASC,
[RerunCounter] ASC
));
-- 48992126 rows
CREATE TABLE [dbo].[AJF](
[JobName] [nvarchar](255) NOT NULL,
[JobNo] [int] NOT NULL,
[OrderNo] [int] NOT NULL,
[Odate] [datetime] NOT NULL,
[SchedTab] [nvarchar](255) NULL,
[Application] [nvarchar](255) NULL,
[ApplGroup] [nvarchar](255) NULL,
[GroupName] [nvarchar](255) NULL,
[NodeID] [nvarchar](255) NULL,
[Memlib] [nvarchar](255) NULL,
[Memname] [nvarchar](255) NULL,
[CreationTime] [datetime] NULL,
CONSTRAINT [AJF$PrimaryKey] PRIMARY KEY CLUSTERED
( [JobName] ASC,
[JobNo] ASC,
[OrderNo] ASC,
[Odate] ASC
));
-- 413176 rows
CREATE TABLE [dbo].[DsAvg](
[JobName] [nvarchar](255) NULL,
[GroupName] [nvarchar](255) NULL,
[JobStatus] [nvarchar](255) NULL,
[ElapsedSecAVG] [float] NULL,
[CpuMSecAVG] [float] NULL
);
CREATE NONCLUSTERED INDEX [DJS_Dashboard_2] ON [dbo].[DsJobStat]
( [JobName] ASC,
[Odate] ASC,
[StartTime] ASC,
[EndTime] ASC
)
INCLUDE ( [OrderID],
[JobNo],
[NodeID],
[GroupName],
[JobStatus],
[CpuMSec],
[ElapsedSec],
[NumericOrderNo]) ;
CREATE NONCLUSTERED INDEX [Idx_Dashboard_AJF] ON [dbo].[AJF]
( [OrderNo] ASC,
[Odate] ASC
)
INCLUDE ( [SchedTab],
[Application],
[ApplGroup]) ;
CREATE NONCLUSTERED INDEX [DsAvg$JobName] ON [dbo].[DsAvg]
( [JobName] ASC
)
Ausführungsplan:
https://www.brentozar.com/pastetheplan/?id=rkUVhMlXM
Update nach Beantwortung
Vielen Dank @Joe Obbish
Sie haben Recht mit dem Problem dieser Abfrage, die zwischen DsJobStat und DsAvg auftritt. Es geht nicht viel darum, sich anzumelden und NOT IN nicht zu verwenden.
Es gibt tatsächlich einen Tisch, wie Sie vermutet haben.
CREATE TABLE [dbo].[DSJobNames](
[JobName] [nvarchar](255) NOT NULL,
CONSTRAINT [DSJobNames$PrimaryKey] PRIMARY KEY CLUSTERED
( [JobName] ASC
) );
Ich habe Ihren Vorschlag ausprobiert,
SELECT DsJobStat.JobName AS JobName
, AJF.ApplGroup AS GroupName
, DsJobStat.JobStatus AS JobStatus
, AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) AS ElapsedSecAVG
, Avg(CAST(DsJobStat.CpuMSec AS FLOAT)) AS CpuMSecAVG
FROM DsJobStat
INNER JOIN DSJobNames jn
ON jn.[JobName]= DsJobStat.[JobName]
INNER JOIN AJF
ON DsJobStat.Odate=AJF.Odate
AND DsJobStat.NumericOrderNo=AJF.OrderNo
WHERE NOT EXISTS ( SELECT 1 FROM [DsAvg] WHERE jn.JobName = [DsAvg].JobName )
GROUP BY DsJobStat.JobName, AJF.ApplGroup, DsJobStat.JobStatus
HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;
Ausführungsnachricht:
(0 row(s) affected)
Table 'DSJobNames'. Scan count 5, logical reads 1244, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsAvg'. Scan count 5, logical reads 2129, physical reads 0, read-ahead reads 24, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsJobStat'. Scan count 8, logical reads 84, physical reads 0, read-ahead reads 83, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AJF'. Scan count 5, logical reads 757999, physical reads 944, read-ahead reads 757311, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row(s) affected)
SQL Server Execution Times:
CPU time = 21776 ms, elapsed time = 33984 ms.
Ausführungsplan: https://www.brentozar.com/pastetheplan/?id=rJVkLSZ7f
Antworten:
Beginnen wir mit der Betrachtung der Beitrittsreihenfolge. Sie haben drei Tabellenreferenzen in der Abfrage. Welche Beitrittsreihenfolge bietet Ihnen möglicherweise die beste Leistung? Der Abfrageoptimierer geht davon aus, dass durch die Verknüpfung von
DsJobStat
bisDsAvg
fast alle Zeilen entfernt werden (Kardinalitätsschätzungen fallen von 212195000 auf 1 Zeile). Der tatsächliche Plan zeigt uns, dass die Schätzung der Realität ziemlich nahe kommt (11 Zeilen überleben den Join). Der Join wird jedoch als rechter Anti-Semi-Merge-Join implementiert, sodass alle 212 Millionen Zeilen aus derDsJobStat
Tabelle gescannt werden, um nur 11 Zeilen zu erzeugen. Das könnte sicherlich zur langen Ausführungszeit der Abfrage beitragen, aber ich kann mir keinen besseren physischen oder logischen Operator für diesen Join vorstellen, der besser gewesen wäre. Ich bin mir sicher, dass dieDJS_Dashboard_2
Der Index wird für andere Abfragen verwendet, aber alle zusätzlichen Schlüssel und enthaltenen Spalten erfordern nur mehr E / A für diese Abfrage und verlangsamen Sie. Sie haben also möglicherweise ein Tabellenzugriffsproblem mit dem Index-Scan für dieDsJobStat
Tabelle.Ich gehe davon aus, dass der Beitritt zu
AJF
nicht sehr selektiv ist. Es ist derzeit nicht relevant für die Leistungsprobleme, die in der Abfrage angezeigt werden. Daher werde ich es für den Rest dieser Antwort ignorieren. Das könnte sich ändern, wenn sich die Daten in der Tabelle ändern.Das andere Problem, das aus dem Plan hervorgeht, ist der Zeilenanzahl-Spool-Operator. Dies ist ein sehr leichter Bediener, der jedoch über 200 Millionen Mal ausgeführt wird. Der Operator ist da, weil die Abfrage mit geschrieben wurde
NOT IN
. Wenn es eine einzelne NULL-Zeile gibt,DsAvg
müssen alle Zeilen entfernt werden. Die Spool ist die Implementierung dieser Prüfung. Das ist wahrscheinlich nicht die Logik, die Sie wollen, also sollten Sie diesen Teil besser schreiben, um ihn zu verwendenNOT EXISTS
. Der tatsächliche Nutzen dieses Umschreibens hängt von Ihrem System und Ihren Daten ab.Ich habe einige Daten basierend auf dem Abfrageplan verspottet, um einige Umschreibungen von Abfragen zu testen. Meine Tabellendefinitionen unterscheiden sich erheblich von Ihren, da es zu aufwändig gewesen wäre, Daten für jede einzelne Spalte zu verspotten. Selbst mit den abgekürzten Datenstrukturen konnte ich das aufgetretene Leistungsproblem reproduzieren.
Anhand des Abfrageplans können wir feststellen, dass
JobName
dieDsAvg
Tabelle etwa 200000 eindeutige Werte enthält . Anhand der tatsächlichen Anzahl der Zeilen nach dem Join zu dieser Tabelle können wir sehen, dass fast alleJobName
Werte inDsJobStat
auch in derDsAvg
Tabelle enthalten sind. Somit enthält dieDsJobStat
Tabelle 200001 eindeutige Werte für dieJobName
Spalte und 1000 Zeilen pro Wert.Ich glaube, dass diese Abfrage das Leistungsproblem darstellt:
Alle anderen Sachen in der Abfrage - Plan (
GROUP BY
,HAVING
, alten Stil verbinden, usw.) geschieht , nachdem die Ergebnismenge auf 11 Zeilen reduziert. Unter dem Gesichtspunkt der Abfrageleistung spielt dies derzeit keine Rolle, es können jedoch auch andere Bedenken auftreten, die durch geänderte Daten in Ihren Tabellen aufgedeckt werden können.Ich teste in SQL Server 2017, erhalte jedoch die gleiche Grundplanform wie Sie:
Auf meinem Computer benötigt diese Abfrage 62219 ms CPU-Zeit und 65576 ms verstrichene Zeit, um ausgeführt zu werden. Wenn ich die zu verwendende Abfrage neu schreibe
NOT EXISTS
:Die Spool wird nicht mehr 212 Millionen Mal ausgeführt und hat wahrscheinlich das vom Hersteller beabsichtigte Verhalten. Jetzt wird die Abfrage in 34516 ms CPU-Zeit und 41132 ms verstrichener Zeit ausgeführt. Die meiste Zeit wird damit verbracht, 212 Millionen Zeilen aus dem Index zu scannen.
Dieser Index-Scan ist für diese Abfrage sehr unglücklich. Im Durchschnitt haben wir 1000 Zeilen pro eindeutigem Wert von
JobName
, aber wir wissen nach dem Lesen der ersten Zeile, ob wir die vorhergehenden 1000 Zeilen benötigen. Wir brauchen diese Zeilen fast nie, aber wir müssen sie trotzdem scannen. Wenn wir wissen, dass die Zeilen in der Tabelle nicht sehr dicht sind und dass fast alle durch den Join eliminiert werden, können wir uns ein möglicherweise effizienteres E / A-Muster im Index vorstellen. Was passiert, wenn SQL Server die erste Zeile pro eindeutigem Wert von liestJobName
, prüft, ob dieser Wert vorhanden istDsAvg
, und einfach zum nächsten Wert überspringt,JobName
wenn dies der Fall ist? Anstatt 212 Millionen Zeilen zu scannen, könnte stattdessen ein Suchplan erstellt werden, der etwa 200.000 Ausführungen erfordert.Dies kann meistens durch Rekursion zusammen mit einer von Paul White entwickelten Technik erreicht werden, die hier beschrieben wird . Wir können die Rekursion verwenden, um das oben beschriebene E / A-Muster zu erstellen:
Diese Abfrage ist sehr wichtig, daher empfehle ich, den tatsächlichen Plan sorgfältig zu prüfen . Zuerst führen wir 200002 Indexsuchen gegen den Index durch
DsJobStat
, um alle eindeutigenJobName
Werte zu erhalten. Dann verbinden wirDsAvg
alle Zeilen bis auf eine. Schließen Sie sich für die verbleibende Zeile wieder anDsJobStat
und rufen Sie alle erforderlichen Spalten ab.Das E / A-Muster ändert sich vollständig. Bevor wir das bekamen:
Mit der rekursiven Abfrage erhalten wir Folgendes:
Auf meinem Computer wird die neue Abfrage in nur 6891 ms CPU-Zeit und 7107 ms verstrichener Zeit ausgeführt. Beachten Sie, dass die Verwendung der Rekursion auf diese Weise darauf hindeutet, dass im Datenmodell etwas fehlt (oder dass es in der geposteten Frage einfach nicht angegeben wurde). Wenn es eine relativ kleine Tabelle gibt, die alles Mögliche enthält, ist
JobNames
es viel besser, diese Tabelle zu verwenden, als eine Rekursion auf der großen Tabelle. Wenn Sie eine Ergebnismenge haben, die alles enthältJobNames
, was Sie benötigen, können Sie Indexsuchen verwenden, um den Rest der fehlenden Spalten abzurufen. Mit einer Ergebnismenge von können Sie dies jedoch nicht tunJobNames
die Sie NICHT benötigen.quelle
NOT EXISTS
. Sie antworteten bereits mit "Ich habe bereits beide ausprobiert, bin beigetreten und nicht vorhanden, bevor ich eine Frage gestellt habe. Nicht viel Unterschied."Sehen Sie, was passiert, wenn Sie die Bedingung neu schreiben.
Zu
Überlegen Sie auch, Ihren SQL89-Join neu zu schreiben, da dieser Stil schrecklich ist.
Anstatt
Versuchen
Ich vermute auch, dass diese Bedingung besser geschrieben werden kann, aber wir müssten mehr darüber wissen, was passiert
Müssen Sie wirklich wissen, dass der Durchschnitt nicht Null ist oder dass nur ein Element der Gruppe nicht Null ist?
quelle