Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Join-Eliminierung:Wenn SQL Server unnötige Tabellen entfernt

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.

Über den Autor

Bert ist ein Business-Intelligence-Entwickler aus Cleveland, Ohio. Er liebt es, schnell ausgeführte Abfragen zu schreiben, und hilft anderen gerne dabei, selbstständige SQL-Problemlöser zu werden. Bert bloggt unter bertwagner.com über SQL Server und erstellt YouTube-Videos zu SQL Server unter youtube.com/c/bertwagner.