Améliorez les performances des requêtes à l'aide de IN ()

14

J'ai la requête SQL suivante:

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;

J'ai également un index sur la Eventtable pour la colonne TimeStamp. Je crois comprendre que cet index n'est pas utilisé à cause de la IN()déclaration. Donc, ma question est de savoir s'il existe un moyen de créer un index pour cette IN()instruction particulière afin d'accélérer cette requête?

J'ai également essayé d'ajouter en Event.EventTypeID IN (2, 5, 7, 8, 9, 14)tant que filtre pour l'index TimeStamp, mais en regardant le plan d'exécution, il ne semble pas utiliser cet index. Toutes suggestions ou informations à ce sujet seraient grandement appréciées.

Voici le plan graphique:

Plan d'exécution

Et voici un lien vers le fichier .sqlplan .

SandersKY
la source
Pourrions-nous également examiner le plan d'exécution? :)
dezso
1
Et veuillez publier le plan d'exécution réel (non estimé) avec l'extension .sqlplan. La plupart des gens veulent simplement publier une capture d'écran du plan graphique, ce qui est beaucoup moins utile.
Aaron Bertrand
OK, j'ai ajouté un plan d'exécution et mis à jour la requête SQL.
SandersKY
@SandersKY Il est préférable d'insérer le fichier .sqlplan pour conserver tout ce qui concerne la question sur le même site.
Trygve Laugstøl
1
@trygvis - Cela n'était souvent pas possible en raison des limitations de longueur des messages. L'échange de pile de honte ne prend pas en charge l'hébergement de pièces jointes en interne.
Martin Smith

Réponses:

18

Étant donné les tableaux de la forme générale suivante:

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

L'index suivant est utile:

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

Pour la requête:

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

Le filtre répond à l' ANDexigence de la clause, la première clé de l'index permet une recherche sur [TimeStamp]le filtré EventTypeIDset l'inclusion de la DeviceIDcolonne rend l'index couvrant (car il DeviceIDest requis pour la jointure à la Devicetable).

Plan terminé

La deuxième clé de l'index - EventTypeIDn'est pas strictement requise (il peut également s'agir d'une INCLUDEdcolonne); Je l'ai inclus dans la clé pour les raisons indiquées ici . En général, je conseille aux gens d'au moins des INCLUDEcolonnes d'une WHEREclause d' index filtré .


Sur la base de la mise à jour de la requête et du plan d'exécution dans la question, je conviens que l'index plus général suggéré par SSMS est probablement le meilleur choix ici, à moins que la liste des filtrés ne EventTypeIDssoit statique comme Aaron le mentionne également dans sa réponse:

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

Index suggéré (le déclarer unique si cela est approprié):

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

Informations de cardinalité du plan d'exécution (syntaxe non documentée, ne pas utiliser dans les systèmes de production):

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;

Requête mise à jour (la répétition de la INliste pour la EventTypetable aide l'optimiseur dans ce cas spécifique):

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;

Plan d'exécution estimé:

Deuxième plan

Le plan que vous obtiendrez sera probablement différent, car j'utilise des statistiques devinées. L'idée générale est de fournir à l'optimiseur autant d'informations que possible et de fournir une méthode d'accès efficace (index) sur la [Event]table de 4 millions de lignes .

Paul White 9
la source
8

La majorité du coût est l'analyse d'index en cluster, et à moins que cette table ne soit vraiment large ou que vous n'ayez pas vraiment besoin de toutes ces colonnes dans la sortie, je crois que SQL Server est le chemin optimal dans le scénario actuel avec rien d'autre changé . Il utilise un balayage de plage (étiqueté comme une recherche de CI) pour réduire la plage de lignes qui l'intéresse, mais en raison de la sortie, il va toujours nécessiter une recherche ou un balayage de CI, même avec l'index filtré que vous avez créé qui est ciblé sur cette plage, et même dans ce cas, l'analyse CI est probablement toujours la moins chère (ou du moins SQL Server l'estime comme telle).

Le plan d'exécution vous dit que cet index serait utile:

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

Bien que selon votre asymétrie de données, il pourrait être préférable de l'inverse, par exemple:

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

Mais je testerais les deux pour être sûr de ce qui est le mieux, si l'un ou l'autre - la différence entre l'un de ces indices et ce que vous avez maintenant ne peut être que marginale (trop de variables pour que nous le sachions) et vous devez tenir compte du fait qu'un supplément l'index nécessite une maintenance supplémentaire, ce qui peut sensiblement affecter vos opérations DML (insérer / mettre à jour / supprimer). Vous pouvez également envisager d'inclure les critères de filtrage dans cet index comme suggéré par @SQLKiwi , mais uniquement s'il s'agit de l'ensemble de valeurs EventTypeID que vous recherchez fréquemment. Si cet ensemble change au fil du temps, l'index filtré ne sera utile que pour cette requête spécifique.

Avec un nombre de lignes aussi bas, je dois me demander à quel point les performances pourraient être mauvaises actuellement? Cette requête renvoie 3 lignes (mais il n'y a aucune indication du nombre de lignes qu'elle a rejetées). Combien de lignes dans le tableau?

Aaron Bertrand
la source
4

Je viens de découvrir que SQL Server 2008 R2 a effectivement fait une suggestion d'index lorsque j'ai exécuté le plan d'exécution. Cet index suggéré accélère l'exécution de la requête d'environ 90%.

L'indice qu'il a suggéré était le suivant:

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
la source