Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Eliminação de junção:quando o SQL Server remove tabelas desnecessárias



Autor convidado:Bert Wagner (@bertwagner)


A eliminação de junção é uma das muitas técnicas que o otimizador de consulta do SQL Server usa para criar planos de consulta eficientes. Especificamente, a eliminação de associação ocorre quando o SQL Server pode estabelecer a igualdade usando a lógica de consulta ou restrições de banco de dados confiáveis ​​para eliminar associações desnecessárias. Veja uma versão em vídeo completa deste post no meu canal do YouTube.

Junte-se à eliminação em ação


A maneira mais simples de explicar a eliminação de junção é através de uma série de demonstrações. Para esses exemplos, usarei o banco de dados de demonstração WideWorldImporters.

Para começar, veremos como a eliminação de junção funciona quando uma chave estrangeira está presente:
SELECT
  	il.*
  FROM
  	Sales.InvoiceLines il
  	INNER JOIN Sales.Invoices i
  		ON il.InvoiceID = i.InvoiceID;

Neste exemplo, estamos retornando dados apenas de Sales.InvoiceLines onde um InvoiceID correspondente é encontrado em Sales.Invoices. Embora você possa esperar que o plano de execução mostre um operador de junção nas tabelas Sales.InvoiceLines e Sales.Invoices, o SQL Server nunca se incomoda em examinar Sales.Invoices:



O SQL Server evita ingressar na tabela Sales.Invoices porque confia na integridade referencial mantida pela restrição de chave estrangeira definida em InvoiceID entre Sales.InvoiceLines e Sales.Invoices; se existir uma linha em Sales.InvoiceLines, uma linha com o valor correspondente para InvoiceID deverá existem em Sales.Invoices. E como estamos retornando apenas dados da tabela Sales.InvoiceLines, o SQL Server não precisa ler nenhuma página de Sales.Invoices.

Podemos verificar se o SQL Server está usando a restrição de chave estrangeira para eliminar a junção, descartando a restrição e executando nossa consulta novamente:
ALTER TABLE [Sales].[InvoiceLines]  
DROP CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices];



Sem informações sobre o relacionamento entre nossas duas tabelas, o SQL Server é forçado a realizar uma junção, verificando um índice em nossa tabela Sales.Invoices para encontrar InvoiceIDs correspondentes.

Do ponto de vista de E/S, o SQL Server deve ler 124 páginas extras de um índice na tabela Sales.Invoices, e isso ocorre apenas porque ele pode usar um índice estreito (coluna única) criado por uma restrição de chave estrangeira diferente. Esse cenário pode ser muito pior em tabelas maiores ou tabelas que não são indexadas adequadamente.


Limitações


Embora o exemplo anterior mostre o básico de como funciona a eliminação de junção, precisamos estar cientes de algumas ressalvas.

Primeiro, vamos adicionar de volta nossa restrição de chave estrangeira:
ALTER TABLE [Sales].[InvoiceLines]  
  WITH NOCHECK ADD  CONSTRAINT 
  [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices] FOREIGN KEY([InvoiceID])
REFERENCES [Sales].[Invoices] ([InvoiceID]);

Se executarmos nossa consulta de amostra novamente, perceberemos que não obtemos um plano que exiba a eliminação de junção; em vez disso, obtemos um plano que verifica ambas as nossas tabelas unidas.

O motivo disso ocorre porque, quando adicionamos novamente nossa restrição de chave estrangeira, o SQL Server não sabe se algum dado foi modificado nesse meio tempo. Quaisquer dados novos ou alterados podem não aderir a essa restrição, portanto, o SQL Server não pode confiar na validade de nossos dados:
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');



Para restabelecer a confiança do SQL Server nessa restrição, devemos verificar sua validade:
ALTER TABLE [Sales].[InvoiceLines] 
WITH CHECK CHECK CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices];

Em tabelas grandes, essa operação pode levar algum tempo, sem mencionar a sobrecarga do SQL Server validando esses dados durante cada modificação de inserção/atualização/exclusão daqui para frente.

Outra limitação é que o SQL Server não pode eliminar tabelas unidas quando a consulta precisa retornar quaisquer dados desses possíveis candidatos à eliminação:
SELECT
	il.*,
	i.InvoiceDate
FROM
	Sales.InvoiceLines il
	INNER JOIN Sales.Invoices i
		ON il.InvoiceID = i.InvoiceID;



A eliminação de junção não ocorre na consulta acima porque estamos solicitando que os dados de Sales.Invoices sejam retornados, forçando o SQL Server a ler os dados dessa tabela.

Por fim, é importante observar que a eliminação de junção não ocorrerá quando a chave estrangeira tiver várias colunas ou se as tabelas estiverem em tempdb. O último é um dos vários motivos pelos quais você não deve tentar resolver problemas de otimização copiando suas tabelas para o tempdb.

Cenários Adicionais


Várias tabelas


A eliminação de junção não se limita apenas a junções internas de duas tabelas e tabelas com restrições de chave estrangeira.

Por exemplo, podemos criar uma tabela adicional que faça referência à nossa coluna 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]);

Unir esta tabela em nossa consulta de amostra original também permitirá que o SQL Server elimine nossa tabela 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;



O SQL Server pode eliminar a tabela Sales.Invoices devido à associação transitiva entre os relacionamentos dessas tabelas.

Restrições exclusivas


Em vez de uma restrição de chave estrangeira, o SQL Server também realizará a eliminação de junção se puder confiar no relacionamento de dados com uma restrição exclusiva:
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;


Juntas externas


Contanto que o SQL Server possa inferir restrições de relacionamento, outros tipos de junções também podem sofrer eliminação de tabela. Por exemplo:
SELECT
	il.InvoiceID
FROM
	Sales.InvoiceLines il
	LEFT JOIN Sales.Invoices i
		ON il.InvoiceID = i.InvoiceID

Como ainda temos nossa restrição de chave estrangeira impondo que cada InvoiceID em Sales.InvoiceLines deve ter um InvoiceID correspondente em Sales.Invoices, o SQL Server não tem problemas para retornar tudo de Sales.InvoiceLInes sem a necessidade de ingressar em Sales.Invoices:


Nenhuma restrição necessária


Se o SQL Server puder garantir que não precisará de dados de uma determinada tabela, ele poderá eliminar uma junção.

Nenhuma eliminação de junção ocorre nesta consulta porque o SQL Server não consegue identificar se a relação entre Sales.Invoices e Sales.InvoiceLines é de 1 para 1, 1 para 0 ou 1 para muitos. Ele é forçado a ler Sales.InvoiceLines para determinar se alguma linha correspondente foi encontrada:
SELECT
	i.InvoiceID
FROM
	Sales.InvoiceLines il
	RIGHT JOIN Sales.Invoices i
		ON il.InvoiceID = i.InvoiceID;



No entanto, se especificarmos que queremos um conjunto DISTINCT de i.InvoiceIDs, cada valor exclusivo de Sales.Invoices retornará do SQL Server, independentemente do relacionamento que essas linhas tenham com 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;


Visualizações


Uma vantagem da eliminação de junção é que ela pode funcionar com visualizações, mesmo se a consulta de visualização subjacente não puder usar a eliminação de associação:
-- 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;


Conclusão


A eliminação de junção é uma otimização que o SQL Server executa quando determina que pode fornecer um conjunto de resultados preciso sem a necessidade de ler dados de todas as tabelas especificadas na consulta enviada. Essa otimização pode fornecer melhorias de desempenho significativas, reduzindo o número de páginas que o SQL Server precisa ler, no entanto, geralmente ocorre à custa da necessidade de manter certas restrições de banco de dados. Podemos refatorar consultas para obter os planos de execução mais simples que a eliminação de junção fornece, no entanto, ter o otimizador de consulta simplificar automaticamente nossos planos removendo junções desnecessárias é um bom benefício.

Mais uma vez, convido você a assistir a versão em vídeo completa deste post.

Sobre o autor

Bert é um desenvolvedor de inteligência de negócios de Cleveland, Ohio. Ele adora escrever consultas de execução rápida e gosta de ajudar os outros a aprender a ser solucionadores de problemas SQL autossuficientes. Bert escreve sobre o SQL Server em bertwagner.com e cria vídeos do SQL Server no YouTube em youtube.com/c/bertwagner.