Migliora le prestazioni della query utilizzando IN ()


14

Ho la seguente query SQL:

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;

Ho anche un indice sul Eventtavolo per la colonna TimeStamp. La mia comprensione è che questo indice non viene utilizzato a causa della IN()dichiarazione. Quindi la mia domanda è: esiste un modo per creare un indice per questa particolare IN()istruzione per accelerare questa query?

Ho anche provato ad aggiungere Event.EventTypeID IN (2, 5, 7, 8, 9, 14)un filtro per l'indice attivo TimeStamp, ma quando si guarda il piano di esecuzione non sembra che stia usando questo indice. Qualsiasi suggerimento o approfondimento in questo sarebbe molto apprezzato.

Di seguito è riportato il piano grafico:

Progetto esecutivo

Ed ecco un link al file .sqlplan .


Potremmo guardare anche al piano di esecuzione? :)
dezso,

1
E si prega di pubblicare il piano di esecuzione effettivo (non stimato) con l'estensione .sqlplan. Molte persone vogliono solo pubblicare una schermata del piano grafico, e questo è molto meno utile.
Aaron Bertrand

OK, ho aggiunto un piano di esecuzione e ho aggiornato la query SQL.
SandersKY,

@SandersKY È meglio incorporare il file .sqlplan per mantenere tutto ciò che è correlato alla domanda nello stesso sito.
Trygve Laugstøl,

1
@trygvis - Questo spesso non sarebbe possibile a causa delle limitazioni di lunghezza sui post. Lo scambio di vergogna nello stack non supporta internamente gli allegati di posta di hosting.
Martin Smith,

Risposte:


18

Dati tabelle del seguente modulo generale:

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
);

Il seguente indice è utile:

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

Per la query:

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);

Il filtro soddisfa i ANDrequisiti della clausola, la prima chiave dell'indice consente di cercare [TimeStamp]il filtro EventTypeIDse includendo la DeviceIDcolonna crea l'indice di copertura (poiché DeviceIDè necessario per l'unione alla Devicetabella).

Piano finito

La seconda chiave dell'indice - EventTypeIDnon è strettamente necessaria (potrebbe anche essere una INCLUDEdcolonna); L'ho incluso nella chiave per i motivi indicati qui . In generale, consiglio alle persone di almeno le INCLUDEcolonne da una WHEREclausola di indice filtrata .


Sulla base della query aggiornata e del piano di esecuzione nella domanda, concordo sul fatto che l'indice più generale suggerito da SSMS è probabilmente la scelta migliore qui, a meno che l'elenco dei filtri non EventTypeIDssia statico come Aaron menziona anche nella sua risposta:

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,
);

Indice suggerito (dichiararlo unico se appropriato):

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

Informazioni sulla cardinalità dal piano di esecuzione (sintassi non documentata, non utilizzare nei sistemi di produzione):

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;

Query aggiornata (ripetere l' INelenco per la EventTypetabella aiuta l'ottimizzatore in questo caso specifico):

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;

Piano di esecuzione stimato:

Secondo piano

Il piano che otterrai sarà probabilmente diverso perché sto usando statistiche indovinate. Il punto generale è fornire all'ottimizzatore quante più informazioni possibili e fornire un metodo di accesso efficiente (indice) sulla [Event]tabella da 4 milioni di righe .


8

La maggior parte del costo è la scansione dell'indice cluster e, a meno che questa tabella non sia davvero ampia o non sia davvero necessario tutte quelle colonne nell'output, credo che SQL Server sia questo il percorso ottimale nello scenario attuale senza nient'altro è cambiato . Utilizza una scansione di intervallo (etichettata come ricerca di elementi della configurazione) per restringere l'intervallo di righe a cui è interessato, ma a causa dell'output richiederà comunque una ricerca o una scansione di elementi della configurazione anche con l'indice filtrato che hai creato è destinato a questo intervallo e anche in quel caso la scansione degli elementi della configurazione è probabilmente ancora più economica (o almeno SQL Server lo stima come tale).

Il piano di esecuzione ti dice che questo indice sarebbe utile:

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

Anche se a seconda dell'inclinazione dei dati potrebbe essere meglio il contrario, ad esempio:

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

Ma testerei entrambi per essere sicuro di quale sia il migliore, in entrambi i casi - la differenza tra uno di questi indici e quello che hai ora potrebbe essere solo marginale (troppe variabili per noi da sapere) e devi tenere conto che un ulteriore L'indice richiede una manutenzione aggiuntiva e ciò può influire notevolmente sulle operazioni DML (inserire / aggiornare / eliminare). Puoi anche considerare di includere i criteri di filtro in questo indice come suggerito da @SQLKiwi , ma solo se si tratta dell'insieme di valori EventTypeID che cerchi frequentemente. Se quell'insieme cambia nel tempo, l'indice filtrato sarà utile solo per questa specifica query.

Con un conteggio di righe così basso, devo chiedermi quanto possa essere brutta la performance attualmente? Questa query restituisce 3 righe (ma non vi è alcuna indicazione di quante righe ha rifiutato). Quante righe nella tabella?


4

Ho appena scoperto che SQL Server 2008 R2 ha effettivamente fornito un suggerimento sull'indice quando ho eseguito il piano di esecuzione. Questo indice suggerito velocizza l'esecuzione della query di circa il 90%.

L'indice suggerito era il seguente:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.