Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Élimination des jointures :lorsque SQL Server supprime les tables inutiles

Auteur invité :Bert Wagner (@bertwagner)

L'élimination des jointures est l'une des nombreuses techniques utilisées par l'optimiseur de requête SQL Server pour créer des plans de requête efficaces. Plus précisément, l'élimination des jointures se produit lorsque SQL Server peut établir l'égalité en utilisant une logique de requête ou des contraintes de base de données approuvées pour éliminer les jointures inutiles. Voir une version vidéo complète de cet article sur ma chaîne YouTube.

Rejoignez l'élimination en action

La manière la plus simple d'expliquer l'élimination des jointures consiste à effectuer une série de démonstrations. Pour ces exemples, j'utiliserai la base de données de démonstration WideWorldImporters.

Pour commencer, nous allons voir comment fonctionne l'élimination des jointures lorsqu'une clé étrangère est présente :

SELECT
  	il.*
  FROM
  	Sales.InvoiceLines il
  	INNER JOIN Sales.Invoices i
  		ON il.InvoiceID = i.InvoiceID;

Dans cet exemple, nous renvoyons uniquement les données de Sales.InvoiceLines où un InvoiceID correspondant est trouvé dans Sales.Invoices. Bien que vous puissiez vous attendre à ce que le plan d'exécution affiche un opérateur de jointure sur les tables Sales.InvoiceLines et Sales.Invoices, SQL Server ne prend jamais la peine de consulter Sales.Invoices :

SQL Server évite de se joindre à la table Sales.Invoices car il fait confiance à l'intégrité référentielle maintenue par la contrainte de clé étrangère définie sur InvoiceID entre Sales.InvoiceLines et Sales.Invoices; si une ligne existe dans Sales.InvoiceLines, une ligne avec la valeur correspondante pour InvoiceID doit existent dans Sales.Invoices. Et comme nous renvoyons uniquement les données de la table Sales.InvoiceLines, SQL Server n'a pas du tout besoin de lire les pages de Sales.Invoices.

Nous pouvons vérifier que SQL Server utilise la contrainte de clé étrangère pour éliminer la jointure en supprimant la contrainte et en exécutant à nouveau notre requête :

ALTER TABLE [Sales].[InvoiceLines]  
DROP CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices];

En l'absence d'informations sur la relation entre nos deux tables, SQL Server est obligé d'effectuer une jointure, en analysant un index sur notre table Sales.Invoices pour trouver les InvoiceID correspondants.

Du point de vue des E/S, SQL Server doit lire 124 pages supplémentaires à partir d'un index sur la table Sales.Invoices, et c'est uniquement parce qu'il est capable d'utiliser un index étroit (colonne unique) créé par une contrainte de clé étrangère différente. Ce scénario pourrait se dérouler bien pire sur des tables plus grandes ou des tables qui ne sont pas indexées de manière appropriée.

Limites

Bien que l'exemple précédent montre les bases du fonctionnement de l'élimination des jointures, nous devons être conscients de quelques mises en garde.

Tout d'abord, rajoutons notre contrainte de clé étrangère :

ALTER TABLE [Sales].[InvoiceLines]  
  WITH NOCHECK ADD  CONSTRAINT 
  [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices] FOREIGN KEY([InvoiceID])
REFERENCES [Sales].[Invoices] ([InvoiceID]);

Si nous exécutons à nouveau notre exemple de requête, nous remarquerons que nous n'obtenons pas de plan qui présente l'élimination des jointures ; à la place, nous obtenons un plan qui analyse nos deux tables jointes.

La raison pour laquelle cela se produit est que, lorsque nous avons rajouté notre contrainte de clé étrangère, SQL Server ne sait pas si des données ont été modifiées entre-temps. Toute donnée nouvelle ou modifiée peut ne pas respecter cette contrainte, donc SQL Server ne peut pas faire confiance à la validité de nos données :

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

Pour rétablir la confiance de SQL Server dans cette contrainte, nous devons vérifier sa validité :

ALTER TABLE [Sales].[InvoiceLines] 
WITH CHECK CHECK CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices];

Sur les grandes tables, cette opération peut prendre un certain temps, sans parler de la surcharge de SQL Server qui valide ces données lors de chaque modification d'insertion/mise à jour/suppression.

Une autre limitation est que SQL Server ne peut pas éliminer les tables jointes lorsque la requête doit renvoyer des données de ces candidats potentiels à l'élimination :

SELECT
	il.*,
	i.InvoiceDate
FROM
	Sales.InvoiceLines il
	INNER JOIN Sales.Invoices i
		ON il.InvoiceID = i.InvoiceID;

L'élimination des jointures ne se produit pas dans la requête ci-dessus, car nous demandons que les données de Sales.Invoices soient renvoyées, ce qui oblige SQL Server à lire les données de cette table.

Enfin, il est important de noter que l'élimination des jointures ne se produira pas lorsque la clé étrangère comporte plusieurs colonnes ou si les tables sont dans tempdb. Cette dernière est l'une des nombreuses raisons pour lesquelles vous ne devriez pas essayer de résoudre les problèmes d'optimisation en copiant vos tables dans tempdb.

Scénarios supplémentaires

Tableaux multiples

L'élimination des jointures ne se limite pas aux jointures internes à deux tables et aux tables avec des contraintes de clé étrangère.

Par exemple, nous pouvons créer une table supplémentaire qui référence notre colonne Sales.Invoices.InvoiceID :

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

Joindre cette table à notre exemple de requête d'origine permettra également à SQL Server d'éliminer notre table Sales.Invoices :

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 peut éliminer la table Sales.Invoices en raison de l'association transitive entre les relations de ces tables.

Contraintes uniques

Au lieu d'une contrainte de clé étrangère, SQL Server effectuera également une élimination de jointure s'il peut faire confiance à la relation de données avec une contrainte unique :

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;

Jointures externes

Tant que SQL Server peut déduire des contraintes de relation, d'autres types de jointures peuvent également subir l'élimination de table. Par exemple :

SELECT
	il.InvoiceID
FROM
	Sales.InvoiceLines il
	LEFT JOIN Sales.Invoices i
		ON il.InvoiceID = i.InvoiceID

Étant donné que nous avons toujours notre contrainte de clé étrangère imposant que chaque InvoiceID dans Sales.InvoiceLines doit avoir un InvoiceID correspondant dans Sales.Invoices, SQL Server n'a aucun problème à tout renvoyer de Sales.InvoiceLInes sans avoir besoin de se joindre à Sales.Invoices :

Aucune contrainte requise

Si SQL Server peut garantir qu'il n'aura pas besoin des données d'une certaine table, il peut potentiellement éliminer une jointure.

Aucune élimination de jointure ne se produit dans cette requête car SQL Server ne peut pas identifier si la relation entre Sales.Invoices et Sales.InvoiceLines est 1-to-1, 1-to-0 ou 1-to-many. Il est obligé de lire Sales.InvoiceLines pour déterminer si des lignes correspondantes sont trouvées :

SELECT
	i.InvoiceID
FROM
	Sales.InvoiceLines il
	RIGHT JOIN Sales.Invoices i
		ON il.InvoiceID = i.InvoiceID;

Cependant, si nous spécifions que nous voulons un ensemble DISTINCT d'i.InvoiceID, chaque valeur unique de Sales.Invoice est renvoyée par SQL Server, quelle que soit la relation entre ces lignes et Sales.InvoiceLines.

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

Vues

L'un des avantages de l'élimination des jointures est qu'elle peut fonctionner avec des vues, même si la requête de vue sous-jacente ne peut pas utiliser l'élimination des jointures :

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

Conclusion

L'élimination des jointures est une optimisation effectuée par SQL Server lorsqu'il détermine qu'il peut fournir un ensemble de résultats précis sans avoir à lire les données de toutes les tables spécifiées dans la requête soumise. Cette optimisation peut fournir des améliorations significatives des performances en réduisant le nombre de pages que SQL Server doit lire, mais elle se fait souvent au détriment de la nécessité de maintenir certaines contraintes de base de données. Nous pouvons refactoriser les requêtes pour obtenir les plans d'exécution plus simples fournis par l'élimination des jointures, mais le fait que l'optimiseur de requêtes simplifie automatiquement nos plans en supprimant les jointures inutiles est un avantage appréciable.

Encore une fois, je vous invite à regarder la version vidéo complète de cet article.

À propos de l'auteur

Bert est un développeur d'informatique décisionnelle de Cleveland, Ohio. Il adore écrire des requêtes rapides et aime aider les autres à apprendre à résoudre les problèmes SQL de manière autonome. Bert blogue sur SQL Server sur bertwagner.com et crée des vidéos YouTube SQL Server sur youtube.com/c/bertwagner.