J'essaie d'améliorer cette (sous-) requête en faisant partie d'une requête plus large:
select SUM(isnull(IP.Q, 0)) as Q,
IP.OPID
from IP
inner join I
on I.ID = IP.IID
where
IP.Deleted=0 and
(I.Status > 0 AND I.Status <= 19)
group by IP.OPID
Sentry Plan Explorer a souligné quelques recherches de clés relativement coûteuses pour la table dbo. [I] effectuées par la requête ci-dessus.
Tableau dbo.I
CREATE TABLE [dbo].[I] (
[ID] UNIQUEIDENTIFIER NOT NULL,
[OID] UNIQUEIDENTIFIER NOT NULL,
[] UNIQUEIDENTIFIER NOT NULL,
[] UNIQUEIDENTIFIER NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NOT NULL,
[] CHAR (3) NOT NULL,
[] CHAR (3) DEFAULT ('EUR') NOT NULL,
[] DECIMAL (18, 8) DEFAULT ((1)) NOT NULL,
[] CHAR (10) NOT NULL,
[] DECIMAL (18, 8) DEFAULT ((1)) NOT NULL,
[] DATETIME DEFAULT (getdate()) NOT NULL,
[] VARCHAR (35) NULL,
[] NVARCHAR (100) NOT NULL,
[] NVARCHAR (100) NULL,
[] NTEXT NULL,
[] NTEXT NULL,
[] NTEXT NULL,
[] NTEXT NULL,
[Status] INT DEFAULT ((0)) NOT NULL,
[] DECIMAL (18, 2) NOT NULL,
[] DECIMAL (18, 2) NOT NULL,
[] DECIMAL (18, 2) NOT NULL,
[] DATETIME DEFAULT (getdate()) NULL,
[] DATETIME NULL,
[] NTEXT NULL,
[] NTEXT NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] DATETIME NULL,
[] VARCHAR (50) NULL,
[] DATETIME DEFAULT (getdate()) NOT NULL,
[] VARCHAR (50) NOT NULL,
[] DATETIME NULL,
[] VARCHAR (50) NULL,
[] ROWVERSION NOT NULL,
[] DATETIME NULL,
[] INT NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] NVARCHAR (50) NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] DECIMAL (18, 2) NULL,
[] DECIMAL (18, 2) NULL,
[] DECIMAL (18, 2) DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] DATETIME NULL,
[] DATETIME NULL,
[] VARCHAR (35) NULL,
[] DECIMAL (18, 2) DEFAULT ((0)) NOT NULL,
CONSTRAINT [PK_I] PRIMARY KEY NONCLUSTERED ([ID] ASC) WITH (FILLFACTOR = 90),
CONSTRAINT [FK_I_O] FOREIGN KEY ([OID]) REFERENCES [dbo].[O] ([ID]),
CONSTRAINT [FK_I_Status] FOREIGN KEY ([Status]) REFERENCES [dbo].[T_Status] ([Status])
);
GO
CREATE CLUSTERED INDEX [CIX_Invoice]
ON [dbo].[I]([OID] ASC) WITH (FILLFACTOR = 90);
Tableau dbo.IP
CREATE TABLE [dbo].[IP] (
[ID] UNIQUEIDENTIFIER DEFAULT (newid()) NOT NULL,
[IID] UNIQUEIDENTIFIER NOT NULL,
[OID] UNIQUEIDENTIFIER NOT NULL,
[Deleted] TINYINT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[]UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] INT NOT NULL,
[] VARCHAR (35) NULL,
[] NVARCHAR (100) NOT NULL,
[] NTEXT NULL,
[] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
[] NTEXT NULL,
[] NTEXT NULL,
[] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
[] DECIMAL (18, 4) DEFAULT ((0)) NOT NULL,
[] DECIMAL (4, 2) NOT NULL,
[] INT DEFAULT ((1)) NOT NULL,
[] DATETIME DEFAULT (getdate()) NOT NULL,
[] VARCHAR (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[] DATETIME NULL,
[] VARCHAR (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[] ROWVERSION NOT NULL,
[] INT DEFAULT ((1)) NOT NULL,
[] DATETIME NULL,
[] UNIQUEIDENTIFIER NULL,
[] DECIMAL (18, 4) DEFAULT ((1)) NOT NULL,
[] DECIMAL (18, 4) DEFAULT ((1)) NOT NULL,
[] INT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[]UNIQUEIDENTIFIER NULL,
[]NVARCHAR (35) NULL,
[] VARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] NVARCHAR (35) NULL,
[] UNIQUEIDENTIFIER NULL,
[] VARCHAR (12) NULL,
[] VARCHAR (4) NULL,
[] NVARCHAR (50) NULL,
[] NVARCHAR (50) NULL,
[] VARCHAR (35) NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] NVARCHAR (50) NULL,
[] TINYINT DEFAULT ((0)) NOT NULL,
[] DECIMAL (18, 2) NULL,
[]TINYINT DEFAULT ((1)) NOT NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] UNIQUEIDENTIFIER NULL,
[] TINYINT DEFAULT ((1)) NOT NULL,
CONSTRAINT [PK_IP] PRIMARY KEY NONCLUSTERED ([ID] ASC) WITH (FILLFACTOR = 90),
CONSTRAINT [FK_IP_I] FOREIGN KEY ([IID]) REFERENCES [dbo].[I] ([ID]) ON DELETE CASCADE NOT FOR REPLICATION,
CONSTRAINT [FK_IP_XType] FOREIGN KEY ([XType]) REFERENCES [dbo].[xTYPE] ([Value]) NOT FOR REPLICATION
);
GO
CREATE CLUSTERED INDEX [IX_IP_CLUST]
ON [dbo].[IP]([IID] ASC) WITH (FILLFACTOR = 90);
Le tableau "I" compte environ 100 000 lignes, l'index cluster compte 9 386 pages.
La table IP est la table "enfant" de I et compte environ 175 000 lignes.
J'ai essayé d'ajouter un nouvel index en suivant la règle d'ordre des colonnes d'index: "WHERE-JOIN-ORDER- (SELECT)"
pour traiter les recherches de clés et créer une recherche d'index:
CREATE NONCLUSTERED INDEX [IX_I_Status_1]
ON [dbo].[Invoice]([Status], [ID])
La requête extraite a immédiatement utilisé cet index. Mais la plus grande requête d'origine dont il fait partie ne l'a pas fait. Il ne l'a même pas utilisé lorsque je l'ai forcé à utiliser WITH (INDEX (IX_I_Status_1)).
Après un certain temps, j'ai décidé d'essayer un autre nouvel index et j'ai changé l'ordre des colonnes indexées:
CREATE NONCLUSTERED INDEX [IX_I_Status_2]
ON [dbo].[Invoice]([ID], [Status])
WOHA! Cet index a été utilisé par la requête extraite et également par la requête plus grande!
J'ai ensuite comparé les statistiques d'E / S des requêtes extraites en l'obligeant à utiliser [IX_I_Status_1] et [IX_I_Status_2]:
Résultats [IX_I_Status_1]:
Table 'I'. Scan count 5, logical reads 636, physical reads 16, read-ahead reads 574
Table 'IP'. Scan count 5, logical reads 1134, physical reads 11, read-ahead reads 1040
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0
Résultats [IX_I_Status_2]:
Table 'I'. Scan count 1, logical reads 615, physical reads 6, read-ahead reads 631
Table 'IP'. Scan count 1, logical reads 1024, physical reads 5, read-ahead reads 1040
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0
OK, je pouvais comprendre que la requête méga-grand-monstre était peut-être trop complexe pour que SQL Server intercepte le plan d'exécution idéal et pourrait manquer mon nouvel index. Mais je ne comprends pas pourquoi l'index [IX_I_Status_2] semble être plus adapté et plus efficace pour la requête.
Étant donné que la requête filtre tout d'abord la table I par colonne STATUS, puis la joint à la table IP, je ne comprends pas pourquoi [IX_I_Status_2] est meilleur et utilisé par Sql Server au lieu de [IX_I_Status_1]?
Réponses:
À tout le moins, il s'agit de conseils incomplets et potentiellement trompeurs (je n'ai pas pris la peine de lire l'intégralité de l'article). Si vous allez lire des choses sur Internet (y compris cela), vous devez ajuster votre niveau de confiance en fonction de la façon dont vous connaissez et faites déjà confiance à l'auteur, mais vérifiez toujours par vous-même.
Il existe un certain nombre de «règles de base» pour créer des index, selon le scénario exact, mais aucune n'est vraiment un bon substitut pour comprendre les problèmes de base par vous-même. Renseignez-vous sur la mise en œuvre des index et des opérateurs de plan d'exécution dans SQL Server, passez par quelques exercices et apprenez bien comment les index peuvent être utilisés pour rendre les plans d'exécution plus efficaces. Il n'y a pas de raccourci efficace pour atteindre ces connaissances et cette expérience.
En général, je peux dire que vos index devraient avoir le plus souvent des colonnes utilisées pour les tests d'égalité en premier, avec toutes les inégalités en dernier, et / ou fournies par un filtre sur l'index. Il ne s'agit pas d'une instruction complète, car les index peuvent également fournir de l'ordre, ce qui peut être plus utile que de rechercher directement une ou plusieurs clés dans certaines situations. Par exemple, l'ordre peut être utilisé pour éviter un tri, pour réduire le coût d'une option de jointure physique comme fusionner la jointure, pour activer un agrégat de flux, trouver rapidement les premières lignes éligibles ... et ainsi de suite.
Je suis un peu vague ici, car la sélection du ou des index idéaux pour une requête dépend de nombreux facteurs - c'est un sujet très large.
Quoi qu'il en soit, il n'est pas rare de trouver des signaux contradictoires pour les «meilleurs» index dans une requête. Par exemple, votre prédicat de jointure souhaiterait que les lignes soient ordonnées dans un sens pour une jointure de fusion, le groupe par souhaiterait que les lignes soient triées d'une autre manière pour un agrégat de flux, et trouver les lignes qualifiées à l'aide de la clause where les prédicats suggéreraient d'autres index.
La raison pour laquelle l'indexation est à la fois un art et une science est qu'une combinaison idéale n'est pas toujours logiquement possible. Le choix des meilleurs index de compromis pour la charge de travail (et pas seulement une seule requête) nécessite des compétences analytiques, de l'expérience et des connaissances spécifiques au système. Si c'était facile , les outils automatisés seraient parfaits et les consultants en optimisation des performances seraient beaucoup moins demandés.
En ce qui concerne les suggestions d'index manquantes: elles sont opportunistes. L'optimiseur les porte à votre attention lorsqu'il essaie de faire correspondre les prédicats et l'ordre de tri requis à un index qui n'existe pas. Les suggestions sont donc basées sur des tentatives d'appariement particulières dans le contexte spécifique de la variation particulière du sous-plan qu'elle envisageait à l'époque.
Dans le contexte, les suggestions ont toujours un sens, en termes de réduction du coût estimé de l'accès aux données, selon le modèle de l'optimiseur. Il ne fait pas une analyse plus large de la requête dans son ensemble (et encore moins de la charge de travail plus large), vous devez donc considérer ces suggestions comme un indice léger qu'une personne qualifiée doit consulter les index disponibles, avec les suggestions comme point de départ. point (et généralement pas plus que cela).
Dans votre cas, la
(Status) INCLUDE (ID)
suggestion est probablement venue quand elle a examiné la possibilité d'un hachage ou d'une fusion (par exemple plus tard). Dans ce contexte étroit, la suggestion est logique. Pour la requête dans son ensemble, peut-être pas. L'index(ID, Status)
permet une jointure de boucle imbriquée avecID
comme référence externe: recherche d'égalitéID
et inégalité àStatus
chaque itération.Une sélection possible d'index est:
... qui produit un plan comme:
Je ne dis pas que ces index sont optimaux pour vous; il m'arrive de travailler pour produire un plan raisonnable pour moi, sans pouvoir voir les statistiques des tables impliquées, ni les définitions complètes et l'indexation existante. De plus, je ne sais rien de la charge de travail plus large ou de la vraie requête.
Alternativement (juste pour montrer l'une des innombrables possibilités supplémentaires):
Donne:
Les plans d'exécution ont été générés à l'aide de SQL Sentry Plan Explorer .
la source