So optimieren Sie die Abfrage

9

Ich habe eine ähnliche Datenbankstruktur,

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

Der Zweck der DispatchLink-Tabelle besteht darin, zwei Dispatch-Datensätze miteinander zu verknüpfen. Übrigens verwende ich aufgrund von Legacy einen zusammengesetzten Primärschlüssel für meine Versandtabelle, daher kann ich das nicht ohne große Schmerzen ändern. Auch die Link-Tabelle ist möglicherweise nicht der richtige Weg, dies zu tun? Aber wieder Vermächtnis.

Also meine Frage, ob ich diese Abfrage ausführe

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

Ich kann es nie dazu bringen, eine Indexsuche für die DispatchLink-Tabelle durchzuführen. Es wird immer ein vollständiger Index-Scan durchgeführt. Das ist mit ein paar Datensätzen in Ordnung, aber wenn Sie 50000 in dieser Tabelle haben, werden 50000 Datensätze im Index gemäß dem Abfrageplan gescannt. Dies liegt daran, dass die Join-Klausel 'ands' und 'ors' enthält, aber ich kann mir nicht vorstellen, warum SQL nicht stattdessen ein paar Indexsuchen durchführen kann, eine für die linke Seite des 'or'. und eine für die rechte Seite des 'oder'.

Ich möchte eine Erklärung dafür, keinen Vorschlag, die Abfrage schneller zu machen, es sei denn, dies kann ohne Anpassung der Abfrage erfolgen. Der Grund dafür ist, dass ich die obige Abfrage als Filter für die Zusammenführung von Replikationsverknüpfungen verwende, sodass ich leider nicht einfach einen anderen Abfragetyp hinzufügen kann.

UPDATE: Dies sind zum Beispiel die Arten von Indizes, die ich hinzugefügt habe.

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)

Es verwendet also die Indizes, führt jedoch einen Index-Scan über den gesamten Index durch, sodass 50000 Datensätze 50000 Datensätze im Index scannen.

Peter
quelle
Haben Sie einen Index auf dem DispatchLinkTisch?
Ypercubeᵀᴹ
Ich habe die Indizes hinzugefügt, die ich oben ausprobiert habe.
Peter
In Ihrer Abfrage: "Wählen Sie * aus Dispatch d inner join DispatchLink dl auf d.DispatchId = dl.DispatchLink1 und d.ContractId = dl.ContractLink1 oder d.DispatchId = dl.DispatchLink2 und d.ContractId = dl.ContractLink2" Verwenden Sie die Bedingung "ODER" und ersetzen Sie sie durch UNION mit 2 SELECT-Anweisungen, die jeweils kein "ODER" verwenden. Verwenden Sie auch die einzigen Schlüsselspalten in beiden SELECTs anstelle des "*", um den Test so rein wie möglich zu gestalten.
NoChance
Dank SQL Kiwi habe ich dies zuvor versucht, aber es hat leider nicht funktioniert.
Peter
1
Kann bei der Replikation eine einfachere Abfrage auftreten: Wählen Sie * aus Dispatch d inner join DispatchLink dl auf d.DispatchId = dl.DispatchLink1 und d.ContractId = dl.ContractLink1 aus. Wenn ja, können wir Daten in DispatchLink duplizieren, damit die Ergebnisse weiterhin gültig sind ...
AK

Antworten:

12

Das Optimierungsprogramm kann viele Planalternativen berücksichtigen (einschließlich solcher mit mehreren Suchvorgängen), bei Disjunktionen ( ORPrädikaten) werden jedoch standardmäßig keine Pläne mit Indexschnittpunkten berücksichtigt. Angesichts der Indizes:

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

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

Wir können Indexsuchen erzwingen (vorausgesetzt, SQL Server 2008 oder höher):

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

FORCESEEK-Plan

Unter Verwendung Ihrer Beispieldaten kostet der Suchplan 0,0332551 Einheiten im Vergleich zu 0,0068057 für den Scanplan :

Scanplan

Es gibt alle möglichen Umschreibungen und Hinweise für Abfragen, die wir ausprobieren können. Ein Beispiel für ein Umschreiben, um eine Option zu fördern, die der Optimierer für den ursprünglichen Plan nicht berücksichtigt, ist:

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;

Dieser Ausführungsplan sucht nicht nach dem zweiten Index, wenn er eine Übereinstimmung mit dem ersten findet:

TOP-Plan ANWENDEN

Dies kann etwas besser als der Standardplan sein FORCESEEK.

Ohne neue Indizes hinzuzufügen, können wir auch eine Suche in die Dispatch-Tabelle erzwingen:

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

Suche 2

Dies kann besser oder schlechter sein als das erste Beispiel, abhängig davon, wie viele Zeilen in jeder der Tabellen enthalten sind. Die APPLY + TOPVerbesserung ist noch möglich:

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
quelle
Das ist eine sehr nützliche Antwort. Ich habe eine andere Frage gestellt: dba.stackexchange.com/questions/23773/analysing-a-query-plan, die den tatsächlichen Abfrageplan für reale Daten (nicht meine Testdaten) anzeigt . Ich habe nicht das Wissen, um genau zu verstehen, was der Engpass im Abfrageplan ist. Vielleicht können Sie einen Blick darauf werfen?
Peter
Es ist wirklich interessant, weil durch Hinzufügen von 'FORCESEEK' meine Abfrage in 9 Sekunden ausgeführt wird, anstatt mehr als 10 Minuten zu dauern. Statistiken aktualisieren macht keinen Unterschied. Warum sollte der Abfrageanalysator sonst so etwas falsch machen?
Peter
Ich denke, Sie haben Recht mit dem Design. Was meinst du mit dem Wiederholen von Spalten? Wie würden Sie eine Tabellenstruktur entwerfen, die zwei Dispatch-Datensätze als verknüpft miteinander verknüpfen müsste? Um zu verdeutlichen, dass die 'echte' Tabelle ein eigenes Primärschlüsselfeld hat, hilft es nicht gerade, einen zusammengesetzten Schlüssel in Dispatch zu haben.
Peter
SQL Kiwi. Spalten wiederholen. Habe ich, danke.
Peter