Autor invitado:Bert Wagner (@bertwagner)
La eliminación de combinaciones es una de las muchas técnicas que utiliza el optimizador de consultas de SQL Server para crear planes de consulta eficientes. Específicamente, la eliminación de combinaciones ocurre cuando SQL Server puede establecer la igualdad mediante la lógica de consulta o las restricciones de bases de datos confiables para eliminar las combinaciones innecesarias. Vea una versión completa en video de esta publicación en mi canal de YouTube.
Únase a la eliminación en acción
La forma más sencilla de explicar la eliminación de combinaciones es a través de una serie de demostraciones. Para estos ejemplos, utilizaré la base de datos de demostración de WideWorldImporters.
Para empezar, veremos cómo funciona la eliminación de combinaciones cuando hay una clave externa presente:
SELECT il.* FROM Sales.InvoiceLines il INNER JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID;
En este ejemplo, solo devolvemos datos de Sales.InvoiceLines donde se encuentra un InvoiceID coincidente en Sales.Invoices. Si bien puede esperar que el plan de ejecución muestre un operador de unión en las tablas Sales.InvoiceLines y Sales.Invoices, SQL Server nunca se molesta en mirar Sales.Invoices en absoluto:
SQL Server evita unirse a la tabla Sales.Invoices porque confía en la integridad referencial mantenida por la restricción de clave externa definida en InvoiceID entre Sales.InvoiceLines y Sales.Invoices; si existe una fila en Sales.InvoiceLines, una fila con el valor coincidente para InvoiceID debe existen en Ventas.Facturas. Y dado que solo devolvemos datos de la tabla Sales.InvoiceLines, SQL Server no necesita leer ninguna página de Sales.Invoices en absoluto.
Podemos verificar que SQL Server está utilizando la restricción de clave externa para eliminar la unión eliminando la restricción y ejecutando nuestra consulta nuevamente:
ALTER TABLE [Sales].[InvoiceLines] DROP CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices];
Sin información sobre la relación entre nuestras dos tablas, SQL Server se ve obligado a realizar una unión, escaneando un índice en nuestra tabla Sales.Invoices para encontrar los Id. de factura coincidentes.
Desde el punto de vista de E/S, SQL Server debe leer 124 páginas adicionales de un índice en la tabla Sales.Invoices, y eso es solo porque puede usar un índice estrecho (columna única) creado por una restricción de clave externa diferente. Este escenario podría ser mucho peor en tablas más grandes o tablas que no están indexadas adecuadamente.
Limitaciones
Si bien el ejemplo anterior muestra los conceptos básicos de cómo funciona la eliminación de combinaciones, debemos tener en cuenta algunas advertencias.
Primero, volvamos a agregar nuestra restricción de clave externa:
ALTER TABLE [Sales].[InvoiceLines] WITH NOCHECK ADD CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices] FOREIGN KEY([InvoiceID]) REFERENCES [Sales].[Invoices] ([InvoiceID]);
Si ejecutamos nuestra consulta de muestra nuevamente, notaremos que no obtenemos un plan que muestre la eliminación de combinación; en su lugar, obtenemos un plan que escanea nuestras dos tablas unidas.
La razón por la que esto ocurre es porque, cuando volvimos a agregar nuestra restricción de clave externa, SQL Server no sabe si se ha modificado algún dato mientras tanto. Es posible que los datos nuevos o modificados no cumplan con esta restricción, por lo que SQL Server no puede confiar en la validez de nuestros datos:
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 restablecer la confianza de SQL Server en esta restricción, debemos verificar su validez:
ALTER TABLE [Sales].[InvoiceLines] WITH CHECK CHECK CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices];
En tablas grandes, esta operación puede llevar algún tiempo, sin mencionar la sobrecarga de SQL Server al validar estos datos durante cada modificación de inserción/actualización/eliminación en el futuro.
Otra limitación es que SQL Server no puede eliminar tablas unidas cuando la consulta necesita devolver datos de esos posibles candidatos a eliminación:
SELECT il.*, i.InvoiceDate FROM Sales.InvoiceLines il INNER JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID;
La eliminación de unión no ocurre en la consulta anterior porque estamos solicitando que se devuelvan los datos de Sales.Invoices, lo que obliga a SQL Server a leer los datos de esa tabla.
Finalmente, es importante tener en cuenta que la eliminación de unión no ocurrirá cuando la clave externa tenga varias columnas o si las tablas están en tempdb. Esta última es una de varias razones por las que no debería intentar resolver problemas de optimización copiando sus tablas en tempdb.
Escenarios adicionales
Múltiples tablas
La eliminación de combinaciones no solo se limita a combinaciones internas de dos tablas y tablas con restricciones de clave externa.
Por ejemplo, podemos crear una tabla adicional que haga referencia a nuestra columna 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 tabla a nuestra consulta de muestra original también permitirá que SQL Server elimine nuestra tabla 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 puede eliminar la tabla Sales.Invoices debido a la asociación transitiva entre las relaciones de estas tablas.
Restricciones únicas
En lugar de una restricción de clave externa, SQL Server también realizará la eliminación conjunta si puede confiar en la relación de datos con una restricción única:
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;
Uniones externas
Siempre que SQL Server pueda inferir restricciones de relación, otros tipos de uniones también pueden experimentar la eliminación de tablas. Por ejemplo:
SELECT il.InvoiceID FROM Sales.InvoiceLines il LEFT JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID
Dado que todavía tenemos nuestra restricción de clave externa que impone que cada Id. de factura en Sales.InvoiceLines debe tener un Id. de factura correspondiente en Sales.Invoices, SQL Server no tiene problemas para devolver todo desde Sales.InvoiceLINEs sin la necesidad de unirse a Sales.Invoices:
No se requieren restricciones
Si SQL Server puede garantizar que no necesitará datos de una determinada tabla, potencialmente puede eliminar una unión.
No se produce ninguna eliminación de unión en esta consulta porque SQL Server no puede identificar si la relación entre Sales.Invoices y Sales.InvoiceLines es 1 a 1, 1 a 0 o 1 a muchos. Se ve obligado a leer Sales.InvoiceLines para determinar si se encuentran filas coincidentes:
SELECT i.InvoiceID FROM Sales.InvoiceLines il RIGHT JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID;
Sin embargo, si especificamos que queremos un conjunto DISTINTO de i.InvoiceIDs, cada valor único de Sales.Invoices regresa de SQL Server, independientemente de la relación que tengan esas filas con 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;
Visualizaciones
Una ventaja de la eliminación de combinaciones es que puede funcionar con vistas, incluso si la consulta de vista subyacente no puede usar la eliminación de combinaciones:
-- 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;
Conclusión
La eliminación de combinaciones es una optimización que realiza SQL Server cuando determina que puede proporcionar un conjunto de resultados preciso sin necesidad de leer los datos de todas las tablas especificadas en la consulta enviada. Esta optimización puede proporcionar mejoras significativas en el rendimiento al reducir la cantidad de páginas que debe leer SQL Server; sin embargo, a menudo se produce a expensas de la necesidad de mantener ciertas restricciones de la base de datos. Podemos refactorizar las consultas para lograr los planes de ejecución más simples que proporciona la eliminación de combinaciones; sin embargo, tener el optimizador de consultas simplifica automáticamente nuestros planes eliminando las combinaciones innecesarias es un buen beneficio.
Nuevamente, lo invito a ver la versión completa en video de esta publicación.