Comment optimiser la requête

9

J'ai une structure de base de données similaire à celle-ci,

CREATE TABLE [dbo].[Dispatch](
    [DispatchId] [int] NOT NULL,
    [ContractId] [int] NOT NULL,
    [DispatchDescription] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_Dispatch] PRIMARY KEY CLUSTERED 
(
    [DispatchId] ASC,
    [ContractId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE TABLE [dbo].[DispatchLink](
    [ContractLink1] [int] NOT NULL,
    [DispatchLink1] [int] NOT NULL,
    [ContractLink2] [int] NOT NULL,
    [DispatchLink2] [int] NOT NULL
) ON [PRIMARY]

GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (1, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (2, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (3, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (4, 1, N'Test')
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 2)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 3)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 3, 1, 2)
GO

Le point de la table DispatchLink est de lier deux enregistrements Dispatch ensemble. Soit dit en passant, j'utilise une clé primaire composite sur ma table de répartition en raison de l'héritage, donc je ne peux pas changer cela sans trop de peine. De plus, la table des liens n'est peut-être pas la bonne façon de le faire? Mais encore une fois l'héritage.

Donc ma question, si je lance cette requête

select * from Dispatch d
inner join DispatchLink dl on d.DispatchId = dl.DispatchLink1 and d.ContractId = dl.ContractLink1
or d.DispatchId = dl.DispatchLink2 and d.ContractId = dl.ContractLink2

Je n'arrive jamais à le faire faire une recherche d'index sur la table DispatchLink. Il effectue toujours une analyse complète de l'index. C'est très bien avec quelques enregistrements, mais lorsque vous avez 50000 dans cette table, il analyse 50000 enregistrements dans l'index selon le plan de requête. C'est parce qu'il y a des «et» et des «ou» dans la clause join, mais je ne peux pas comprendre pourquoi SQL ne peut pas faire quelques recherches d'index à la place, une pour le côté gauche du «ou», et un pour le côté droit du «ou».

Je voudrais une explication à cela, pas une suggestion pour accélérer la requête, sauf si cela peut être fait sans ajuster la requête. La raison en est que j'utilise la requête ci-dessus comme filtre de jointure de réplication de fusion, donc je ne peux pas simplement ajouter un autre type de requête malheureusement.

MISE À JOUR: Par exemple, ce sont les types d'index que j'ai ajoutés,

CREATE NONCLUSTERED INDEX IDX1 ON DispatchLink (ContractLink1, DispatchLink1)
CREATE NONCLUSTERED INDEX IDX2 ON DispatchLink (ContractLink2, DispatchLink2)
CREATE NONCLUSTERED INDEX IDX3 ON DispatchLink (ContractLink1, DispatchLink1, ContractLink2, DispatchLink2)

Il utilise donc les index, mais effectue une analyse d'index sur l'ensemble de l'index, donc 50000 enregistrements, il analyse 50000 enregistrements dans l'index.

peter
la source
Avez-vous un index sur la DispatchLinktable?
ypercubeᵀᴹ
J'ai ajouté les index que j'ai essayés ci-dessus.
peter
Dans votre requête: "sélectionnez * dans Dispatch d jointure interne DispatchLink dl sur d.DispatchId = dl.DispatchLink1 et d.ContractId = dl.ContractLink1 ou d.DispatchId = dl.DispatchLink2 et d.ContractId = dl.ContractLink2" essayez de supprimer la condition "OR" et la remplacer par UNION de 2 instructions SELECT n'utilisant pas chacune "OR", utilisez également les seules colonnes clés dans les deux SELECT au lieu du "*", juste pour rendre le test aussi pur que possible.
NoChance
Merci SQL Kiwi, c'est quelque chose que j'ai déjà essayé mais cela n'a malheureusement pas fonctionné.
peter
1
Pouvez-vous avoir un problème de réplication plus simple: sélectionnez * dans Dispatch d jointure interne DispatchLink dl sur d.DispatchId = dl.DispatchLink1 et d.ContractId = dl.ContractLink1 Si oui, nous pouvons dupliquer les données dans DispatchLink afin que les résultats soient toujours valides ...
AK

Réponses:

12

L'optimiseur peut prendre en compte de nombreuses alternatives de plan (y compris celles avec plusieurs recherches) mais pour les disjonctions ( ORprédicats), il ne prend pas en compte les plans impliquant des intersections d'index par défaut. Compte tenu des indices:

CREATE CLUSTERED INDEX cx 
ON dbo.DispatchLink (DispatchLink1, ContractLink1);

CREATE NONCLUSTERED INDEX nc1 
ON dbo.DispatchLink (DispatchLink2, ContractLink2);

Nous pouvons forcer la recherche d'index (en supposant que SQL Server 2008 ou version ultérieure):

SELECT * 
FROM dbo.Dispatch AS d
INNER JOIN dbo.DispatchLink AS dl WITH (FORCESEEK) ON 
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

Plan FORCESEEK

En utilisant vos données d'échantillonnage, le plan de recherche coûte 0,0332551 unités par rapport à 0,0068057 pour le plan de numérisation:

Plan de numérisation

Il existe toutes sortes de réécritures de requêtes et d'indices possibles que nous pouvons essayer. Un exemple de réécriture pour promouvoir une option que l'optimiseur ne prend pas en compte pour le plan d'origine est:

SELECT * 
FROM dbo.Dispatch AS d
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;

Ce plan d'exécution ne cherche pas le deuxième index s'il trouve une correspondance sur le premier:

APPLIQUER TOP Plan

Cela peut fonctionner très légèrement mieux que le FORCESEEKplan par défaut .

Sans ajouter de nouveaux index, nous pouvons également forcer une recherche dans la table Dispatch:

SELECT * 
FROM dbo.DispatchLink AS dl
JOIN dbo.Dispatch AS d WITH (FORCESEEK) ON
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

Cherche 2

Cela peut être meilleur ou pire que le premier exemple en fonction de choses comme le nombre de lignes dans chacune des tables. L' APPLY + TOPamélioration est toujours possible:

SELECT * 
FROM dbo.DispatchLink AS dl
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;
Paul White 9
la source
Voilà une réponse très utile. J'ai posé une autre question dba.stackexchange.com/questions/23773/analysing-a-query-plan qui montre le plan de requête réel sur des données réelles (pas mes données de test). Je n'ai pas les connaissances nécessaires pour comprendre exactement quel est le goulot d'étranglement sur le plan de requête. Vous pouvez peut-être jeter un œil?
peter
C'est vraiment intéressant parce que l'ajout de 'FORCESEEK' fait que ma requête s'exécute en 9 secondes plutôt que de prendre plus de 10 minutes. La mise à jour des statistiques ne fait aucune différence. Sinon, pourquoi l'analyseur de requêtes se trompe-t-il?
peter
Je pense que vous avez raison concernant le design. Que voulez-vous dire par répétition de colonnes? Comment concevriez-vous une structure de tableau qui devrait relier deux enregistrements de répartition comme étant liés? Pour clarifier si la «vraie» table a son propre champ de clé primaire, mais oui, avoir une clé composite dans Dispatch n'aide pas vraiment.
peter
SQL Kiwi. Colonnes extensibles. Je l'ai Merci.
Peter