Verbesserung der Abfrageleistung mit IN ()

14

Ich habe folgende SQL-Abfrage:

SELECT
  Event.ID,
  Event.IATA,
  Device.Name,
  EventType.Description,
  Event.Data1,
  Event.Data2
  Event.PLCTimeStamp,
  Event.EventTypeID
FROM
  Event
INNER JOIN EventType ON EventType.ID = Event.EventTypeID
INNER JOIN Device ON Device.ID = Event.DeviceID
WHERE
  Event.EventTypeID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND Event.PLCTimeStamp BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.IATA LIKE '%0005836217%'
ORDER BY Event.ID;

Ich habe auch einen Index auf der EventTabelle für die Spalte TimeStamp. Nach meinem Verständnis wird dieser Index aufgrund der IN()Anweisung nicht verwendet . Meine Frage ist also, ob es eine Möglichkeit gibt, einen Index für diese bestimmte IN()Anweisung zu erstellen, um diese Abfrage zu beschleunigen.

Ich habe auch versucht, Event.EventTypeID IN (2, 5, 7, 8, 9, 14)den Index als Filter hinzuzufügen TimeStamp, aber wenn ich mir den Ausführungsplan ansehe, scheint er diesen Index nicht zu verwenden. Anregungen oder Einblicke in diese würden sehr geschätzt.

Unten ist der grafische Plan:

Ausführungsplan

Und hier ist ein Link zur .sqlplan-Datei .

SandersKY
quelle
Könnten wir uns auch den Ausführungsplan ansehen? :)
dezso
1
Und bitte posten Sie den tatsächlichen Ausführungsplan (nicht geschätzt) mit der Erweiterung .sqlplan. Die meisten Leute möchten nur einen Screenshot des grafischen Plans veröffentlichen, und das ist weitaus weniger nützlich.
Aaron Bertrand
OK Ich habe den Ausführungsplan hinzugefügt und die SQL-Abfrage aktualisiert.
SandersKY
@SandersKY Es ist am besten, die .sqlplan-Datei inline zu setzen, um alles, was mit der Frage zu tun hat, auf derselben Site zu speichern.
Trygve Laugstøl
1
@trygvis - Das wäre aufgrund von Längenbeschränkungen für Posts oft nicht möglich. Shame Stack Exchange unterstützt das interne Hosten von Post-Anhängen nicht.
Martin Smith

Antworten:

18

Gegebene Tabellen der folgenden allgemeinen Form:

CREATE TABLE Device 
(
    ID integer PRIMARY KEY
);

CREATE TABLE EventType
(
    ID integer PRIMARY KEY, 
    Name nvarchar(50) NOT NULL
);

CREATE TABLE [Event]
(
    ID integer PRIMARY KEY, 
    [TimeStamp] datetime NOT NULL, 
    EventTypeID integer NOT NULL REFERENCES EventType, 
    DeviceID integer NOT NULL REFERENCES Device
);

Der folgende Index ist nützlich:

CREATE INDEX f1 
ON [Event] ([TimeStamp], EventTypeID) 
INCLUDE (DeviceID)
WHERE EventTypeID IN (2, 5, 7, 8, 9, 14);

Für die Abfrage:

SELECT
  [Event].ID,
  [Event].[TimeStamp],
  EventType.Name,
  Device.ID
FROM
  [Event]
INNER JOIN EventType ON EventType.ID = [Event].EventTypeID
INNER JOIN Device ON Device.ID = [Event].DeviceID
WHERE
  [Event].[TimeStamp] BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.EventTypeID IN (2, 5, 7, 8, 9, 14);

Der Filter erfüllt die ANDKlauselanforderung, der erste Schlüssel des Index ermöglicht eine Suche [TimeStamp]nach dem gefilterten EventTypeIDsund einschließlich der DeviceIDSpalte das Index-Covering (da DeviceIDdies für den Join zur DeviceTabelle erforderlich ist ).

Fertiger Plan

Der zweite Schlüssel des Index EventTypeIDist nicht unbedingt erforderlich (es kann sich auch um eine INCLUDEdSpalte handeln). Ich habe es enthalten im Schlüssel für die hier genannten Gründe . Im Allgemeinen rate ich Leuten, zumindest INCLUDESpalten aus einer gefilterten WHEREIndexklausel zu verwenden.


Aufgrund des aktualisierten Abfrage- und Ausführungsplans in der Frage stimme ich zu, dass der von SSMS vorgeschlagene allgemeinere Index wahrscheinlich die bessere Wahl ist, es sei denn, die gefilterte Liste EventTypeIDsist statisch, wie Aaron auch in seiner Antwort erwähnt:

CREATE TABLE Device 
(
    ID integer PRIMARY KEY,
    Name nvarchar(50) NOT NULL UNIQUE
);

CREATE TABLE EventType
(
    ID integer PRIMARY KEY, 
    Name nvarchar(20) NOT NULL UNIQUE,
    [Description] nvarchar(100) NOT NULL
);

CREATE TABLE [Event]
(
    ID integer PRIMARY KEY, 
    PLCTimeStamp datetime NOT NULL,
    EventTypeID integer NOT NULL REFERENCES EventType, 
    DeviceID integer NOT NULL REFERENCES Device,
    IATA varchar(50) NOT NULL,
    Data1 integer NULL,
    Data2 integer NULL,
);

Vorgeschlagener Index (deklarieren Sie ihn als eindeutig, falls dies angemessen ist):

CREATE UNIQUE INDEX uq1
ON [Event]
    (EventTypeID, PLCTimeStamp)
INCLUDE 
    (DeviceID, IATA, Data1, Data2, ID);

Kardinalitätsinformationen aus dem Ausführungsplan (undokumentierte Syntax, nicht in Produktionssystemen verwenden):

UPDATE STATISTICS dbo.Event WITH ROWCOUNT = 4042700, PAGECOUNT = 400000;
UPDATE STATISTICS dbo.EventType WITH ROWCOUNT = 22, PAGECOUNT = 1;
UPDATE STATISTICS dbo.Device WITH ROWCOUNT = 2806, PAGECOUNT = 28;

Aktualisierte Abfrage (Wiederholung der INListe für dieEventType Tabelle hilft dem Optimierer in diesem speziellen Fall):

SELECT
  Event.ID,
  Event.IATA,
  Device.Name,
  EventType.Description,
  Event.Data1,
  Event.Data2,
  Event.PLCTimeStamp,
  Event.EventTypeID
FROM
  Event
INNER JOIN EventType ON EventType.ID = Event.EventTypeID
INNER JOIN Device ON Device.ID = Event.DeviceID
WHERE
  Event.EventTypeID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND EventType.ID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND Event.PLCTimeStamp BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.IATA LIKE '%0005836217%'
ORDER BY Event.ID;

Geschätzter Ausführungsplan:

Zweiter Plan

Der Plan, den Sie erhalten, wird wahrscheinlich anders sein, weil ich erratene Statistiken verwende. Der allgemeine Punkt ist, dem Optimierer so viele Informationen wie möglich zu geben und eine effiziente Zugriffsmethode (Index) für die 4-Millionen-Zeilentabelle bereitzustellen [Event].

Paul White Setzen Sie Monica wieder ein
quelle
8

Der Großteil der Kosten entfällt auf den Clustered-Index-Scan. Sofern diese Tabelle nicht sehr umfangreich ist oder Sie nicht wirklich alle diese Spalten in der Ausgabe benötigen, ist SQL Server meiner Meinung nach der optimale Pfad im aktuellen Szenario, an dem sich nichts anderes geändert hat . Es wird eine Bereichsüberprüfung (als CI-Suche bezeichnet) verwendet, um den Bereich der Zeilen einzugrenzen, an denen es interessiert ist. Aufgrund der Ausgabe ist jedoch auch bei dem von Ihnen erstellten gefilterten Index eine Suche oder eine CI-Überprüfung erforderlich ist auf diesen Bereich ausgerichtet, und selbst in diesem Fall ist der CI-Scan wahrscheinlich immer noch am günstigsten (oder zumindest wird er von SQL Server als solcher eingeschätzt).

Der Ausführungsplan sagt Ihnen, dass dieser Index nützlich wäre:

CREATE NONCLUSTERED INDEX ix_EventTypeID_PLCTimeStamp_WithIncludes
  ON [dbo].[Event] ([EventTypeID],[PLCTimeStamp])
  INCLUDE ([ID],[DeviceID],[Data1],[Data2],[IATA]);

Abhängig von Ihrer Datenverschiebung ist es möglicherweise umgekehrt besser, z. B .:

CREATE NONCLUSTERED INDEX ix_PLCTimeStamp_EventTypeID_WithIncludes
  ON [dbo].[Event] ([PLCTimeStamp],[EventTypeID])
  INCLUDE ([ID],[DeviceID],[Data1],[Data2],[IATA]);

Aber ich würde beides testen, um sicherzugehen, welches besser ist, wenn entweder - der Unterschied zwischen einem dieser Indizes und dem, was Sie jetzt haben, ist möglicherweise nur marginal (zu viele Variablen, als dass wir sie kennen), und Sie müssen dies zusätzlich berücksichtigen Index erfordert zusätzliche Wartung, und dies kann Ihre DML-Vorgänge (Einfügen / Aktualisieren / Löschen) spürbar beeinträchtigen. Sie können auch in Betracht ziehen, die Filterkriterien in diesen Index aufzunehmen, wie von @SQLKiwi vorgeschlagen , jedoch nur dann, wenn dies der Satz von EventTypeID-Werten ist, nach denen Sie häufig suchen. Wenn sich dieser Satz im Laufe der Zeit ändert, ist der gefilterte Index nur für diese bestimmte Abfrage nützlich.

Bei so einer geringen Zeilenanzahl muss ich mich fragen, wie schlecht die Leistung derzeit sein könnte? Diese Abfrage gibt 3 Zeilen zurück (es gibt jedoch keinen Hinweis darauf, wie viele Zeilen sie zurückgewiesen hat). Wie viele Zeilen in der Tabelle?

Aaron Bertrand
quelle
4

Ich habe gerade festgestellt, dass SQL Server 2008 R2 tatsächlich einen Indexvorschlag gemacht hat, als ich den Ausführungsplan ausgeführt habe. Durch diesen vorgeschlagenen Index wird die Abfrage etwa 90% schneller ausgeführt.

Der Index, den es vorschlug, war der folgende:

CREATE NONCLUSTERED INDEX [INDEX_spBagSearch] ON [dbo].[Event] 
(
    [EventTypeID] ASC,
    [PLCTimeStamp] ASC
)
INCLUDE ( [ID],
[DeviceID],
[Data1],
[Data2],
[IATA]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO
SandersKY
quelle