TL; DR
この質問は何度も意見を得ているので、ここに要約しますので、初心者が歴史に苦しむ必要はありません。
JOIN table t ON t.member = @value1 OR t.member = @value2 -- this is slow as hell
JOIN table t ON t.member = COALESCE(@value1, @value2) -- this is blazing fast
-- Note that here if @value1 has a value, @value2 is NULL, and vice versa
これはすべての人の問題ではないかもしれませんが、ON句の感度を強調することで、正しい方向に進むのに役立つかもしれません。いずれにせよ、元のテキストは将来の人類学者のためにここにあります:
元のテキスト
次の単純なクエリを検討してください(3つのテーブルのみが関係します)。
SELECT
l.sku_id AS ProductId,
l.is_primary AS IsPrimary,
v1.category_name AS Category1,
v2.category_name AS Category2,
v3.category_name AS Category3,
v4.category_name AS Category4,
v5.category_name AS Category5
FROM category c4
JOIN category_voc v4 ON v4.category_id = c4.category_id and v4.language_code = 'en'
JOIN category c3 ON c3.category_id = c4.parent_category_id
JOIN category_voc v3 ON v3.category_id = c3.category_id and v3.language_code = 'en'
JOIN category c2 ON c2.category_id = c3.category_id
JOIN category_voc v2 ON v2.category_id = c2.category_id and v2.language_code = 'en'
JOIN category c1 ON c1.category_id = c2.parent_category_id
JOIN category_voc v1 ON v1.category_id = c1.category_id and v1.language_code = 'en'
LEFT OUTER JOIN category c5 ON c5.parent_category_id = c4.category_id
LEFT OUTER JOIN category_voc v5 ON v5.category_id = c5.category_id and v5.language_code = @lang
JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
l.category_id = c4.category_id OR
l.category_id = c5.category_id
)
WHERE c4.[level] = 4 AND c4.version_id = 5
これは非常に単純なクエリであり、唯一混乱する部分は最後のカテゴリ結合です。これは、カテゴリレベル5が存在する場合と存在しない場合があるためです。クエリの最後で、製品ID(SKU ID)ごとのカテゴリ情報を探しています。そこに非常に大きなテーブルcategory_linkがあります。最後に、テーブル#Idsは10'000のIDを含む一時テーブルです。
実行すると、次の実際の実行プランが表示されます。
ご覧のとおり、時間のほぼ90%がネストされたループ(内部結合)に費やされています。これらのネストされたループに関する追加情報を次に示します。
読みやすくするためにクエリテーブル名を編集したため、テーブル名は完全には一致しませんが、一致させるのは簡単です(ads_alt_category = category)。このクエリを最適化する方法はありますか?また、本番環境では、一時テーブル#Idsは存在しません。これは、ストアドプロシージャに渡される同じ10'000 IDのテーブル値パラメーターです。
追加情報:
編集(解決済み)
承認された回答で指摘されているように、問題はcategory_link JOINのOR句です。しかし、承認された回答で提案されたコードは非常に遅く、元のコードよりも遅くなります。多くのより速く、よりクリーンな解決策は、現在のJOIN条件を次のものに置き換えるだけです:
JOIN category_link l on l.sku_id IN (SELECT value FROM @p1) AND l.category_id = COALESCE(c5.category_id, c4.category_id)
この分Tweakは最も速いソリューションであり、受け入れられた回答からの二重結合に対してテストされ、valverrijによって提案されたCROSS APPLYに対してもテストされました。
問題はコードのこの部分にあるようです:
_JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
l.category_id = c4.category_id OR
l.category_id = c5.category_id
)
_
結合条件のor
は常に疑わしいです。 1つの提案は、これを2つの結合に分割することです。
_JOIN category_link l1 on l1.sku_id in (SELECT value FROM #Ids) and l1.category_id = cr.category_id
left outer join
category_link l1 on l2.sku_id in (SELECT value FROM #Ids) and l2.category_id = cr.category_id
_
次に、これを処理するために残りのクエリを変更する必要があります。 。 。 coalesce(l1.sku_id, l2.sku_id)
は、たとえばselect
句で使用します。
別のユーザーが述べたように、この結合が原因である可能性があります:
JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
l.category_id = c4.category_id OR
l.category_id = c5.category_id
)
これらを複数の結合に分割する以外に、 CROSS APPLY
を試すこともできます
CROSS APPLY (
SELECT [some column(s)]
FROM category_link x
WHERE EXISTS(SELECT value FROM #Ids WHERE value = x.sku_id)
AND (x.category_id = c4.category_id OR x.category_id = c5.category_id)
) l
上記のMSDNリンクから:
テーブル値関数は右入力として機能し、外側のテーブル式は左入力として機能します。 右入力は左入力からの各行に対して評価され、生成された行は最終出力に結合されます。
基本的に、APPLY
は、最初に右側のレコードを除外するサブクエリのようなものであり、、次に残りのクエリにそれらを適用します。
この記事は、それが何であり、いつ使用するかを説明するのに非常に優れています。 http://explainextended.com/2009/07/16/inner-join-vs-cross-apply/
ただし、CROSS APPLY
は常にINNER JOIN
よりも高速に動作するとは限らないことに注意することが重要です。多くの場合、それはおそらくほぼ同じです。ただし、まれに、実際にそれが遅くなるのを見たことがあります(これもすべて、テーブル構造とクエリ自体に依存します)。
一般的な経験則として、条件付きステートメントが多すぎるテーブルに参加している場合、APPLY
に傾く傾向があります。
また、楽しいメモ:OUTER APPLY
はLEFT JOIN
のように機能します
また、EXISTS
ではなくIN
を使用するという私の選択に注意してください。サブクエリでIN
を実行する場合、値が見つかった後でも、結果セット全体が返されることに注意してください。ただし、EXISTS
を使用すると、一致が見つかるとすぐにサブクエリが停止します。