web-dev-qa-db-fra.com

Optimisation des requêtes SQL en supprimant l'opérateur de tri dans le plan d'exécution

Je viens de commencer à chercher à optimiser mes requêtes grâce aux index, car les données SQL augmentent rapidement et volumineuses. J'ai regardé comment l'optimiseur traite ma requête via le plan d'exécution dans SSMS et j'ai remarqué qu'un opérateur de tri est utilisé. J'ai entendu dire qu'un opérateur de tri indique une mauvaise conception dans la requête, car le tri peut être effectué prématurément via un index. Voici donc un exemple de tableau et de données similaires à ce que je fais:

IF OBJECT_ID('dbo.Store') IS NOT NULL DROP TABLE dbo.[Store]
GO

CREATE TABLE dbo.[Store]
(
    [StoreId] int NOT NULL IDENTITY (1, 1),
    [ParentStoreId] int NULL,
    [Type] int NULL,
    [Phone] char(10) NULL,
    PRIMARY KEY ([StoreId])
)

INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 0, '2223334444')
INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 0, '3334445555')
INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 1, '0001112222')
INSERT INTO dbo.[Store] ([ParentStoreId], [Type], [Phone]) VALUES (10, 1, '1112223333')
GO

Voici un exemple de requête:

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND ([Type] = 0 OR [Type] = 1)
ORDER BY [Phone]

Je crée un index non cluster pour accélérer la requête:

CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Type], [Phone])

Pour construire l'index IX_Store, je commence par les prédicats simples

[ParentStoreId] = 10
AND ([Type] = 0 OR [Type] = 1)

Ensuite, j'ajoute le [Phone] colonne pour ORDER BY et pour couvrir la sortie SELECT

Ainsi, même lorsque l'index est généré, l'optimiseur utilise toujours l'opérateur de tri (et non le tri d'index) car [Phone] est trié APRÈS [ParentStoreId] ET [Type]. Si je supprime le [Type] colonne de l'index et exécutez la requête:

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
--AND ([Type] = 0 OR [Type] = 1)
ORDER BY [Phone]

Bien sûr, l'opérateur de tri n'est pas utilisé par l'optimiseur car [Phone] est trié par [ParentStoreId].

La question est donc de savoir comment créer un index qui couvrira la requête (y compris le [Type] prédicat) et que l'optimiseur n'utilise pas un tri?

MODIFIER:

La table avec laquelle je travaille compte plus de 20 millions de lignes

23
jodev

Tout d'abord, vous devez vérifier que le tri est en fait un goulot d'étranglement des performances. La durée du tri dépendra du nombre d'éléments à trier et le nombre de magasins pour un magasin parent particulier est susceptible d'être petit. (Cela suppose que l'opérateur de tri est appliqué après l'application de la clause where).

J'ai entendu dire qu'un opérateur de tri indique une mauvaise conception dans la requête, car le tri peut être effectué prématurément via un index

C'est une généralisation excessive. Souvent, un opérateur de tri peut être déplacé de manière triviale dans l'index et, si seules les deux premières lignes de l'ensemble de résultats sont récupérées, il peut considérablement réduire le coût des requêtes, car la base de données n'a plus à extraire toutes les lignes correspondantes (et à les trier tous) pour trouver les premiers, mais peut lire les enregistrements dans l'ordre du jeu de résultats et s'arrêter une fois que suffisamment d'enregistrements sont trouvés.

Dans votre cas, vous semblez récupérer l'intégralité du jeu de résultats, donc un tri qui ne risque pas d'aggraver les choses (sauf si le jeu de résultats est énorme). De plus, dans votre cas, il peut ne pas être trivial de créer un index trié utile, car la clause where contient un ou.

Maintenant, si vous voulez toujours vous débarrasser de cet opérateur de tri, vous pouvez essayer:

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND [Type] in (0, 1)
ORDER BY [Phone]    

Vous pouvez également essayer l'index suivant:

CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Phone], [Type])

pour essayer d'obtenir l'optimiseur de requête pour effectuer une analyse de plage d'index uniquement sur ParentStoreId, puis analyser toutes les lignes correspondantes dans l'index, en les affichant si Type correspond. Cependant, cela risque d'entraîner davantage d'E/S disque et donc de ralentir votre requête plutôt que de l'accélérer.

Edit: En dernier recours, vous pouvez utiliser

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND [Type] = 0
ORDER BY [Phone]

UNION ALL

SELECT [Phone]
FROM [dbo].[Store]
WHERE [ParentStoreId] = 10
AND [Type] = 1
ORDER BY [Phone]

avec

CREATE NONCLUSTERED INDEX IX_Store ON dbo.[Store]([ParentStoreId], [Type], [Phone])

et triez les deux listes sur le serveur d'applications, où vous pouvez fusionner (comme dans le tri par fusion) les listes pré-triées, évitant ainsi un tri complet. Mais c'est vraiment une micro-optimisation qui, tout en accélérant le tri lui-même d'un ordre de grandeur, est peu susceptible d'affecter beaucoup le temps total d'exécution de la requête, car je m'attendrais à ce que le goulot d'étranglement soit les E/S réseau et disque, surtout à la lumière du fait que le disque fera beaucoup d'accès aléatoire car l'index n'est pas en cluster.

18
meriton