ゲスト作成者:Bert Wagner(@bertwagner)
結合の削除は、SQLServerクエリオプティマイザーが効率的なクエリプランを作成するために使用する多くの手法の1つです。具体的には、SQL Serverがクエリロジックまたは信頼できるデータベースの制約を使用して不要な結合を排除することで同等性を確立できる場合に、結合の排除が発生します。私のYouTubeチャンネルでこの投稿の完全なビデオバージョンを参照してください。
EliminationInActionに参加
結合の除去を説明する最も簡単な方法は、一連のデモを使用することです。これらの例では、WideWorldImportersデモデータベースを使用します。
まず、外部キーが存在する場合に結合の削除がどのように機能するかを見ていきます。
SELECT il.* FROM Sales.InvoiceLines il INNER JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID;
この例では、一致するInvoiceIDがSales.InvoicesにあるSales.InvoiceLinesからのみデータを返しています。実行プランでSales.InvoiceLinesテーブルとSales.Invoicesテーブルに結合演算子が表示されることを期待するかもしれませんが、SQLServerはSales.Invoicesを気にすることはありません。
SQL Serverは、Sales.InvoiceLinesとSales.Invoicesの間のInvoiceIDで定義された外部キー制約によって維持される参照整合性を信頼するため、Sales.Invoicesテーブルへの参加を回避します。 Sales.InvoiceLinesに行が存在する場合、InvoiceIDに一致する値を持つ行は必須 Sales.Invoicesに存在します。また、Sales.InvoiceLinesテーブルからのみデータを返すため、SQLServerはSales.Invoicesからページを読み取る必要はまったくありません。
制約を削除してクエリを再実行することにより、SQLServerが外部キー制約を使用して結合を排除していることを確認できます。
ALTER TABLE [Sales].[InvoiceLines] DROP CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices];
2つのテーブル間の関係に関する情報がないため、SQL Serverは結合を実行し、Sales.Invoicesテーブルのインデックスをスキャンして一致するInvoiceIDを見つけます。
I / Oの観点から、SQL ServerはSales.Invoicesテーブルのインデックスから追加の124ページを読み取る必要があります。これは、別の外部キー制約によって作成された狭い(単一列)インデックスを使用できるためです。このシナリオは、大きなテーブルや適切にインデックスが作成されていないテーブルでは、さらに悪化する可能性があります。
制限
前の例は結合の除去がどのように機能するかの基本を示していますが、いくつかの注意点に注意する必要があります。
まず、外部キー制約を追加し直しましょう:
ALTER TABLE [Sales].[InvoiceLines] WITH NOCHECK ADD CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices] FOREIGN KEY([InvoiceID]) REFERENCES [Sales].[Invoices] ([InvoiceID]);
サンプルクエリを再度実行すると、参加の排除を示すプランが得られないことがわかります。代わりに、結合された両方のテーブルをスキャンするプランを取得します。
これが発生する理由は、外部キー制約を再度追加したときに、SQLServerがその間にデータが変更されたかどうかを認識しないためです。新規または変更されたデータはこの制約に準拠していない可能性があるため、SQLServerはデータの有効性を信頼できません。
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');
この制約に対するSQLServerの信頼を再確立するには、その有効性を確認する必要があります。
ALTER TABLE [Sales].[InvoiceLines] WITH CHECK CHECK CONSTRAINT [FK_Sales_InvoiceLines_InvoiceID_Sales_Invoices];
大きなテーブルでは、今後の挿入/更新/削除の変更のたびにこのデータを検証するSQL Serverのオーバーヘッドは言うまでもなく、この操作には時間がかかる場合があります。
もう1つの制限は、クエリがこれらの潜在的な削除候補からデータを返す必要がある場合、SQLServerは結合されたテーブルを削除できないことです。
SELECT il.*, i.InvoiceDate FROM Sales.InvoiceLines il INNER JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID;
上記のクエリでは、Sales.Invoicesからのデータが返され、SQL Serverにそのテーブルからデータを読み取らせるように要求しているため、結合の削除は発生しません。
最後に、外部キーに複数の列がある場合、またはテーブルがtempdbにある場合、結合の削除は発生しないことに注意することが重要です。後者は、テーブルをtempdbにコピーして最適化の問題を解決しようとすべきではないいくつかの理由の1つです。
追加のシナリオ
複数のテーブル
結合の削除は、2つのテーブルの内部結合と外部キー制約のあるテーブルだけに限定されません。
たとえば、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]);
このテーブルを元のサンプルクエリに結合すると、SQLServerで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は、これらのテーブルの関係間の推移的な関連付けにより、Sales.Invoicesテーブルを削除できます。
固有の制約
SQL Serverは、外部キー制約の代わりに、一意の制約を持つデータ関係を信頼できる場合、結合の削除も実行します。
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;
外部結合
SQL Serverが関係の制約を推測できる限り、他の種類の結合でもテーブルが削除される可能性があります。例:
SELECT il.InvoiceID FROM Sales.InvoiceLines il LEFT JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID
Sales.InvoiceLinesのすべてのInvoiceIDに対応するInvoiceIDがSales.Invoicesに含まれている必要があることを強制する外部キー制約がまだあるため、SQL Serverは、Sales.Invoicesに参加しなくてもSales.InvoiceLInesからすべてを返すことに問題はありません。
>
制約は必要ありません
SQL Serverが特定のテーブルのデータを必要としないことを保証できる場合は、結合を排除できる可能性があります。
SQL ServerはSales.InvoicesとSales.InvoiceLinesの関係が1対1、1対0、または1対多のいずれであるかを識別できないため、このクエリでは結合の削除は発生しません。一致する行が見つかったかどうかを判断するために、Sales.InvoiceLinesを読み取る必要があります。
SELECT i.InvoiceID FROM Sales.InvoiceLines il RIGHT JOIN Sales.Invoices i ON il.InvoiceID = i.InvoiceID;
ただし、i.InvoiceIDのDISTINCTセットが必要であると指定した場合、それらの行とSales.InvoiceLinesとの関係に関係なく、Sales.Invoicesからのすべての一意の値がSQLServerから返されます。
-- 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;
ビュー
結合除去の利点の1つは、基になるビュークエリが結合除去を使用できない場合でも、ビューを処理できることです。
-- 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;
結論
結合の削除は、SQL Serverが、送信されたクエリで指定されたすべてのテーブルからデータを読み取る必要なしに正確な結果セットを提供できると判断したときに実行する最適化です。この最適化により、SQL Serverが読み取る必要のあるページ数を減らすことでパフォーマンスを大幅に向上させることができますが、多くの場合、特定のデータベースの制約を維持する必要があります。クエリをリファクタリングして、結合の削除が提供するより単純な実行プランを実現できますが、クエリオプティマイザーを使用すると、不要な結合を削除することでプランが自動的に単純化されます。
繰り返しになりますが、この投稿の完全なビデオバージョンをご覧になることをお勧めします。