Wenn ich meiner Auswahl zwei Spalten hinzufüge, antwortet die Abfrage nicht. Der Spaltentyp ist nvarchar(2000)
. Es ist ein bisschen ungewöhnlich.
- Die SQL Server-Version ist 2014.
- Es gibt nur einen Primärindex.
- Der gesamte Datensatz besteht nur aus 1000 Zeilen.
Hier ist der Ausführungsplan vor ( XML-Showplan ):
Ausführungsplan nach ( XML-Showplan ):
Hier ist die Abfrage:
select top(100)
Batch_Tasks_Queue.id,
btq.id,
Batch_Tasks_Queue.[Parameters], -- this field
btq.[Parameters] -- and this field
from
Batch_Tasks_Queue with(nolock)
inner join Batch_Tasks_Queue btq with(nolock) on Batch_Tasks_Queue.Start_Time < btq.Start_Time
and btq.Start_Time < Batch_Tasks_Queue.Finish_Time
and Batch_Tasks_Queue.id <> btq.id
and btq.Start_Time is not null
and btq.State in (3, 4)
where
Batch_Tasks_Queue.Start_Time is not null
and Batch_Tasks_Queue.State in (3, 4)
and Batch_Tasks_Queue.Operation_Type = btq.Operation_Type
and Batch_Tasks_Queue.Operation_Type not in (23, 24, 25, 26, 27, 28, 30)
order by
Batch_Tasks_Queue.Start_Time desc
Die Gesamtzahl der Ergebnisse beträgt 17 Zeilen. Die schmutzigen Daten (Nolock-Hinweis) sind nicht wichtig.
Hier ist die Tabellenstruktur:
CREATE TABLE [dbo].[Batch_Tasks_Queue](
[Id] [int] NOT NULL,
[OBJ_VERSION] [numeric](8, 0) NOT NULL,
[Operation_Type] [numeric](2, 0) NULL,
[Request_Time] [datetime] NOT NULL,
[Description] [varchar](1000) NULL,
[State] [numeric](1, 0) NOT NULL,
[Start_Time] [datetime] NULL,
[Finish_Time] [datetime] NULL,
[Parameters] [nvarchar](2000) NULL,
[Response] [nvarchar](max) NULL,
[Billing_UserId] [int] NOT NULL,
[Planned_Start_Time] [datetime] NULL,
[Input_FileId] [uniqueidentifier] NULL,
[Output_FileId] [uniqueidentifier] NULL,
[PRIORITY] [numeric](2, 0) NULL,
[EXECUTE_SEQ] [numeric](2, 0) NULL,
[View_Access] [numeric](1, 0) NULL,
[Seeing] [numeric](1, 0) NULL,
CONSTRAINT [PKBachTskQ] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [Batch_Tasks_QueueData]
) ON [Batch_Tasks_QueueData] TEXTIMAGE_ON [Batch_Tasks_QueueData]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] WITH NOCHECK ADD CONSTRAINT [FK0_BtchTskQ_BlngUsr] FOREIGN KEY([Billing_UserId])
REFERENCES [dbo].[BILLING_USER] ([ID])
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] CHECK CONSTRAINT [FK0_BtchTskQ_BlngUsr]
GO
sql-server
query-performance
sql-server-2014
Hamid Fathi
quelle
quelle
Antworten:
Zusammenfassung
Die Hauptprobleme sind:
Einzelheiten
Die beiden Pläne sind grundsätzlich ziemlich ähnlich, obwohl die Leistung sehr unterschiedlich sein kann:
Planen Sie mit den zusätzlichen Spalten
Nehmen Sie zuerst die mit den zusätzlichen Spalten, die nicht in angemessener Zeit abgeschlossen sind:
Die interessanten Funktionen sind:
Start_Time
nicht null,State
3 oder 4 ist undOperation_Type
einer der aufgelisteten Werte ist. Die Tabelle wird einmal vollständig gescannt, wobei jede Zeile gegen die genannten Prädikate getestet wird. Nur Zeilen, die alle Tests bestehen, werden an die Sortierung weitergeleitet. Der Optimierer schätzt, dass 38.283 Zeilen qualifiziert sind.Start_Time DESC
. Dies ist die endgültige Präsentationsreihenfolge, die von der Abfrage angefordert wird.Start_Time
nicht null sind undState
3 oder 4 betragen. Es wird geschätzt, dass bei jeder Iteration 400.875 Zeilen erzeugt werden. Über 94.2791 Iterationen beträgt die Gesamtzahl der Zeilen fast 38 Millionen.Operation_Type
Übereinstimmungen vorliegen, ob derStart_Time
von Knoten 4 kleiner als derStart_Time
von Knoten 5 ist, ob derStart_Time
von Knoten 5 kleiner als derFinish_Time
von Knoten 4 ist und ob die beidenId
Werte nicht übereinstimmen.Die große Ineffizienz liegt offensichtlich in den obigen Schritten 6 und 7. Das vollständige Scannen der Tabelle am Knoten 5 für jede Iteration ist nur geringfügig sinnvoll, wenn dies nur 94 Mal geschieht, wie es der Optimierer vorhersagt. Die ~ 38 Millionen Vergleiche pro Zeile am Knoten 2 sind ebenfalls mit hohen Kosten verbunden.
Entscheidend ist auch, dass die Schätzung des Ziels der Zeilenreihe 93/94 wahrscheinlich falsch ist, da sie von der Verteilung der Werte abhängt. Der Optimierer geht von einer gleichmäßigen Verteilung aus, wenn keine detaillierteren Informationen vorliegen. In einfachen Worten bedeutet dies, dass, wenn erwartet wird, dass 1% der Zeilen in der Tabelle qualifiziert sind, der Optimierer Gründe dafür hat, dass er 100 Zeilen lesen muss, um eine übereinstimmende Zeile zu finden.
Wenn Sie diese Abfrage vollständig ausführen (was sehr lange dauern kann), werden Sie höchstwahrscheinlich feststellen, dass viel mehr als 93/94 Zeilen aus der Sortierung gelesen werden mussten, um schließlich 100 Zeilen zu erstellen. Im schlimmsten Fall wird die 100. Zeile anhand der letzten Zeile aus der Sortierung gefunden. Unter der Annahme, dass die Schätzung des Optimierers auf Knoten 4 korrekt ist, bedeutet dies, dass der Scan auf Knoten 5 38.284 Mal ausgeführt wird, was insgesamt etwa 15 Milliarden Zeilen entspricht. Es könnte mehr sein, wenn die Scan-Schätzungen ebenfalls deaktiviert sind.
Dieser Ausführungsplan enthält auch eine fehlende Indexwarnung:
Das Optimierungsprogramm weist Sie darauf hin, dass das Hinzufügen eines Index zur Tabelle die Leistung verbessern würde.
Planen Sie ohne die zusätzlichen Spalten
Dies ist im Wesentlichen derselbe Plan wie der vorherige, mit der Hinzufügung der Indexspule an Knoten 6 und des Filters an Knoten 5. Die wichtigen Unterschiede sind:
Operation_Type
undStart_Time
mitId
als Nicht-Schlüsselspalte.Operation_Type
,Start_Time
,Finish_Time
, undId
aus der Abtastung an Knoten 4 weitergeleitet werden an den Innenseiten - Zweig als äußere Referenzen.Operation_Type
entspricht den aktuellen äußeren Referenzwert ist , und dasStart_Time
wird in dem durch die definierten BereichStart_Time
undFinish_Time
äußern Referenzen.Id
Werte aus der Indexspule auf Ungleichheit mit dem aktuellen äußeren Referenzwert vonId
.Die wichtigsten Verbesserungen sind:
Operation_Type
,Start_Time
) mitId
als eingeschlossene Spalte ermöglicht die Verknüpfung eines Index verschachtelter Schleifen. Der Index wird verwendet, um bei jeder Iteration nach übereinstimmenden Zeilen zu suchen, anstatt jedes Mal die gesamte Tabelle zu scannen.Nach wie vor enthält das Optimierungsprogramm eine Warnung vor einem fehlenden Index:
Fazit
Der Plan ohne die zusätzlichen Spalten ist schneller, da der Optimierer einen temporären Index für Sie erstellt hat.
Der Plan mit den zusätzlichen Spalten würde die Erstellung des temporären Index verteuern. Die
[Parameters
Spalte] gibtnvarchar(2000)
bis zu 4000 Byte für jede Zeile des Index an. Die zusätzlichen Kosten reichen aus, um den Optimierer davon zu überzeugen, dass sich das Erstellen des temporären Index für jede Ausführung nicht auszahlt.Der Optimierer warnt in beiden Fällen, dass ein permanenter Index eine bessere Lösung wäre. Die ideale Zusammensetzung des Index hängt von Ihrer Arbeitsbelastung ab. Für diese spezielle Abfrage sind die vorgeschlagenen Indizes ein vernünftiger Ausgangspunkt, aber Sie sollten die damit verbundenen Vorteile und Kosten verstehen.
Empfehlung
Eine breite Palette möglicher Indizes wäre für diese Abfrage von Vorteil. Der wichtige Aspekt ist, dass eine Art nicht gruppierter Index benötigt wird. Aus den bereitgestellten Informationen wäre meiner Meinung nach ein angemessener Index:
Ich wäre auch versucht, die Abfrage etwas besser zu organisieren und das Nachschlagen der breiten
[Parameters]
Spalten im Clustered-Index zu verzögern, bis die 100 besten Zeilen gefunden wurden (Id
als Schlüssel):Wenn die
[Parameters]
Spalten nicht benötigt werden, kann die Abfrage vereinfacht werden, um:Der
FORCESEEK
Hinweis soll sicherstellen, dass das Optimierungsprogramm einen Plan für indizierte verschachtelte Schleifen auswählt (es besteht eine kostenbasierte Versuchung für das Optimierungsprogramm, ansonsten einen Hash oder (viele, viele) Zusammenführungsverknüpfungen auszuwählen, was bei dieser Art von nicht gut funktioniert Abfrage in der Praxis. Beide haben große Residuen (viele Elemente pro Bucket im Fall des Hash und viele Rückspulen für die Zusammenführung).Alternative
Wenn die Abfrage (einschließlich ihrer spezifischen Werte) für die Leseleistung besonders kritisch wäre, würde ich stattdessen zwei gefilterte Indizes in Betracht ziehen:
Für die Abfrage, für die die
[Parameters]
Spalte nicht benötigt wird, lautet der geschätzte Plan unter Verwendung der gefilterten Indizes:Der Index-Scan gibt automatisch alle qualifizierenden Zeilen zurück, ohne zusätzliche Prädikate auszuwerten. Für jede Iteration des Joins mit verschachtelten Indexschleifen führt die Indexsuche zwei Suchoperationen aus:
Operation_Type
undState
= 3, dann den Bereich der Suche nachStart_Time
Werten, Rest Prädikat auf derId
Ungleichheit.Operation_Type
undState
= 4, dann Suche nach dem WertebereichStart_Time
, verbleibendes Prädikat für dieId
Ungleichung.Wenn die
[Parameters]
Spalte benötigt wird, fügt der Abfrageplan einfach maximal 100 Singleton-Lookups für jede Tabelle hinzu:Abschließend sollten Sie in Betracht ziehen, die integrierten Standard-Integer-Typen anstelle der
numeric
ggf. zu verwenden.quelle
Bitte erstellen Sie folgenden Index:
quelle