Gastautor:Bert Wagner (@bertwagner)
Die Join-Eliminierung ist eine der vielen Techniken, die der SQL Server-Abfrageoptimierer verwendet, um effiziente Abfragepläne zu erstellen. Die Eliminierung von Joins tritt insbesondere dann auf, wenn SQL Server mithilfe von Abfragelogik oder Einschränkungen für vertrauenswürdige Datenbanken Gleichheit herstellen kann, um unnötige Joins zu eliminieren. Sehen Sie sich eine vollständige Videoversion dieses Beitrags auf meinem YouTube-Kanal an.
Schließen Sie sich Elimination In Action an
Der einfachste Weg, die Eliminierung von Joins zu erklären, ist eine Reihe von Demos. Für diese Beispiele verwende ich die WideWorldImporters-Demodatenbank.
Zu Beginn sehen wir uns an, wie die Join-Eliminierung funktioniert, wenn ein Fremdschlüssel vorhanden ist:
SELECT il.* FROM Sales.InvoiceLines il INNER JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID;
In diesem Beispiel geben wir nur Daten aus Sales.InvoiceLines zurück, wo eine übereinstimmende InvoiceID in Sales.Invoices gefunden wird. Während Sie vielleicht erwarten, dass der Ausführungsplan einen Join-Operator in den Tabellen Sales.InvoiceLines und Sales.Invoices anzeigt, macht sich SQL Server überhaupt nicht die Mühe, Sales.Invoices anzusehen:
SQL Server vermeidet die Verknüpfung mit der Sales.Invoices-Tabelle, da es der referenziellen Integrität vertraut, die durch die Fremdschlüsseleinschränkung aufrechterhalten wird, die für InvoiceID zwischen Sales.InvoiceLines und Sales.Invoices definiert ist; wenn eine Zeile in Sales.InvoiceLines vorhanden ist, muss eine Zeile mit dem übereinstimmenden Wert für InvoiceID existieren in Sales.Invoices. Und da wir nur Daten aus der Sales.InvoiceLines-Tabelle zurückgeben, muss SQL Server überhaupt keine Seiten aus Sales.Invoices lesen.
Wir können überprüfen, ob SQL Server die Fremdschlüsseleinschränkung verwendet, um den Join zu eliminieren, indem wir die Einschränkung löschen und unsere Abfrage erneut ausführen:
ALTER TABLE [Sales].[InvoiceLines] DROP CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices];
Ohne Informationen über die Beziehung zwischen unseren beiden Tabellen ist SQL Server gezwungen, einen Join durchzuführen und einen Index unserer Sales.Invoices-Tabelle zu durchsuchen, um übereinstimmende InvoiceIDs zu finden.
Aus E/A-Sicht muss SQL Server zusätzliche 124 Seiten aus einem Index in der Tabelle „Sales.Invoices“ lesen, und das nur, weil er in der Lage ist, einen schmalen Index (eine Spalte) zu verwenden, der durch eine andere Fremdschlüsseleinschränkung erstellt wurde. Dieses Szenario könnte bei größeren Tabellen oder Tabellen, die nicht entsprechend indiziert sind, viel schlimmer ablaufen.
Einschränkungen
Während das vorherige Beispiel die Grundlagen der Join-Eliminierung zeigt, müssen wir uns einiger Einschränkungen bewusst sein.
Lassen Sie uns zuerst unsere Fremdschlüsselbeschränkung hinzufügen:
ALTER TABLE [Sales].[InvoiceLines] WITH NOCHECK ADD CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices] FOREIGN KEY([InvoiceID]) REFERENCES [Sales].[Invoices] ([InvoiceID]);
Wenn wir unsere Beispielabfrage erneut ausführen, werden wir feststellen, dass wir keinen Plan erhalten, der eine Join-Eliminierung aufweist. Stattdessen erhalten wir einen Plan, der unsere beiden verbundenen Tabellen scannt.
Der Grund dafür ist, dass SQL Server beim erneuten Hinzufügen unserer Fremdschlüsseleinschränkung nicht weiß, ob in der Zwischenzeit Daten geändert wurden. Alle neuen oder geänderten Daten entsprechen möglicherweise nicht dieser Einschränkung, sodass SQL Server der Gültigkeit unserer Daten nicht vertrauen kann:
SELECT f.name AS foreign_key_name ,OBJECT_NAME(f.parent_object_id) AS table_name ,COL_NAME(fc.parent_object_id, fc.parent_column_id) AS constraint_column_name ,OBJECT_NAME (f.referenced_object_id) AS referenced_object ,COL_NAME(fc.referenced_object_id, fc.referenced_column_id) AS referenced_column_name ,f.is_not_trusted FROM sys.foreign_keys AS f INNER JOIN sys.foreign_key_columns AS fc ON f.object_id = fc.constraint_object_id WHERE f.parent_object_id = OBJECT_ID('Sales.InvoiceLines');
Um das Vertrauen von SQL Server in diese Einschränkung wiederherzustellen, müssen wir ihre Gültigkeit überprüfen:
ALTER TABLE [Sales].[InvoiceLines] WITH CHECK CHECK CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices];
Bei großen Tabellen kann dieser Vorgang einige Zeit in Anspruch nehmen, ganz zu schweigen von dem Overhead, den SQL Server benötigt, um diese Daten bei jeder künftigen Einfüge-/Aktualisierungs-/Löschänderung zu validieren.
Eine weitere Einschränkung besteht darin, dass SQL Server verbundene Tabellen nicht entfernen kann, wenn die Abfrage Daten von diesen potenziellen Eliminierungskandidaten zurückgeben muss:
SELECT il.*, i.InvoiceDate FROM Sales.InvoiceLines il INNER JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID;
Die Join-Eliminierung tritt in der obigen Abfrage nicht auf, da wir anfordern, dass Daten aus Sales.Invoices zurückgegeben werden, wodurch SQL Server gezwungen wird, Daten aus dieser Tabelle zu lesen.
Schließlich ist es wichtig zu beachten, dass die Join-Eliminierung nicht auftritt, wenn der Fremdschlüssel mehrere Spalten hat oder wenn sich die Tabellen in tempdb befinden. Letzteres ist einer von mehreren Gründen, warum Sie nicht versuchen sollten, Optimierungsprobleme zu lösen, indem Sie Ihre Tabellen in tempdb kopieren.
Zusätzliche Szenarien
Mehrere Tabellen
Die Join-Eliminierung ist nicht nur auf Zwei-Tabellen-Inner-Joins und Tabellen mit Fremdschlüsselbeschränkungen beschränkt.
Beispielsweise können wir eine zusätzliche Tabelle erstellen, die auf unsere Spalte „Sales.Invoices.InvoiceID“ verweist:
CREATE TABLE Sales.InvoiceClickTracking ( InvoiceClickTrackingID bigint IDENTITY PRIMARY KEY, InvoiceID int -- other fields would go here ); GO ALTER TABLE [Sales].[InvoiceClickTracking] WITH CHECK ADD CONSTRAINT [FK_Sales_InvoiceClickTracking_InvoiceID_Sales_Invoices] FOREIGN KEY([InvoiceID]) REFERENCES [Sales].[Invoices] ([InvoiceID]);
Durch das Verbinden dieser Tabelle mit unserer ursprünglichen Beispielabfrage kann SQL Server auch unsere Sales.Invoices-Tabelle eliminieren:
SELECT il.InvoiceID, ict.InvoiceID FROM Sales.InvoiceLines il INNER JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID INNER JOIN Sales.InvoiceClickTracking ict ON i.InvoiceID = ict.InvoiceID;
SQL Server kann die Sales.Invoices-Tabelle aufgrund der transitiven Zuordnung zwischen den Beziehungen dieser Tabellen eliminieren.
Eindeutige Einschränkungen
Anstelle einer Fremdschlüsseleinschränkung führt SQL Server auch eine Join-Eliminierung durch, wenn er der Datenbeziehung mit einer eindeutigen Einschränkung vertrauen kann:
ALTER TABLE [Sales].[InvoiceClickTracking] DROP CONSTRAINT [FK_Sales_InvoiceClickTracking_InvoiceID_Sales_Invoices]; GO ALTER TABLE Sales.InvoiceClickTracking ADD CONSTRAINT UQ_InvoiceID UNIQUE (InvoiceID); GO SELECT i.InvoiceID FROM Sales.InvoiceClickTracking ict RIGHT JOIN Sales.Invoices i ON ict.InvoiceID = i.InvoiceID;
Äußere Verknüpfungen
Solange SQL Server Beziehungseinschränkungen ableiten kann, kann es auch bei anderen Arten von Joins zu einer Tabelleneliminierung kommen. Zum Beispiel:
SELECT il.InvoiceID FROM Sales.InvoiceLines il LEFT JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID
Da wir immer noch unsere Fremdschlüsseleinschränkung haben, die erzwingt, dass jede InvoiceID in Sales.InvoiceLines eine entsprechende InvoiceID in Sales.Invoices haben muss, hat SQL Server kein Problem damit, alles aus Sales.InvoiceLInes zurückzugeben, ohne mit Sales.Invoices verknüpft werden zu müssen:
Keine Einschränkung erforderlich
Wenn SQL Server garantieren kann, dass es keine Daten aus einer bestimmten Tabelle benötigt, kann es möglicherweise einen Join eliminieren.
In dieser Abfrage findet keine Join-Eliminierung statt, da SQL Server nicht erkennen kann, ob die Beziehung zwischen „Sales.Invoices“ und „Sales.InvoiceLines“ 1:1, 1:0 oder 1:n ist. Es wird gezwungen, Sales.InvoiceLines zu lesen, um festzustellen, ob übereinstimmende Zeilen gefunden werden:
SELECT i.InvoiceID FROM Sales.InvoiceLines il RIGHT JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID;
Wenn wir jedoch angeben, dass wir einen DISTINCT-Satz von i.InvoiceIDs wünschen, wird jeder eindeutige Wert von Sales.Invoices von SQL Server zurückgegeben, unabhängig davon, welche Beziehung diese Zeilen zu Sales.InvoiceLines haben.
-- Just to prove no foreign key is at play here ALTER TABLE [Sales].[InvoiceLines] DROP CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices]; GO -- Our distinct result set SELECT DISTINCT i.InvoiceID FROM Sales.InvoiceLines il RIGHT JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID;
Aufrufe
Ein Vorteil der Join-Eliminierung besteht darin, dass sie mit Ansichten arbeiten kann, selbst wenn die zugrunde liegende Ansichtsabfrage die Join-Eliminierung nicht verwenden kann:
-- Add back our FK ALTER TABLE [Sales].[InvoiceLines] WITH CHECK ADD CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices] FOREIGN KEY([InvoiceID]) REFERENCES [Sales].[Invoices] ([InvoiceID]); GO -- Create our view using a query that cannot use join elimination CREATE VIEW Sales.vInvoicesAndInvoiceLines AS SELECT i.InvoiceID, i.InvoiceDate, il.Quantity, il.TaxRate FROM Sales.InvoiceLines il INNER JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID; GO -- Join elimination works because we do not select any -- columns from the underlying Sales.Invoices table SELECT Quantity, TaxRate FROM Sales.vInvoicesAndInvoiceLines;
Schlussfolgerung
Die Join-Eliminierung ist eine Optimierung, die SQL Server durchführt, wenn festgestellt wird, dass ein genaues Resultset bereitgestellt werden kann, ohne dass Daten aus allen Tabellen gelesen werden müssen, die in der übermittelten Abfrage angegeben sind. Diese Optimierung kann zu erheblichen Leistungsverbesserungen führen, indem die Anzahl der Seiten reduziert wird, die SQL Server lesen muss, jedoch geht dies häufig zu Lasten der Notwendigkeit, bestimmte Datenbankeinschränkungen einzuhalten. Wir können Abfragen umgestalten, um die einfacheren Ausführungspläne zu erreichen, die die Eliminierung von Joins bietet, aber es ist ein netter Vorteil, dass der Abfrageoptimierer unsere Pläne automatisch vereinfacht, indem unnötige Joins entfernt werden.
Ich lade Sie erneut ein, sich die vollständige Videoversion dieses Beitrags anzusehen.