web-dev-qa-db-ja.com

LEFT JOIN INNER JOINよりも大幅に高速

600,000レコードを少し超えるテーブル(MainTable)があります。親/子タイプの関係で、2番目のテーブル(JoinTable)を介して自身に結合します。

SELECT   Child.ID, Parent.ID
FROM     MainTable
AS       Child
JOIN     JoinTable
      ON Child.ID = JoinTable.ID
JOIN     MainTable
AS       Parent
      ON Parent.ID = JoinTable.ParentID
     AND Parent.SomeOtherData = Child.SomeOtherData

すべての子レコードには親レコードがあり、JoinTableのデータは正確です。

このクエリを実行すると、文字通り数分で実行されます。ただし、左結合を使用して親に結合すると、実行に1秒未満かかります。

SELECT   Child.ID, Parent.ID
FROM     MainTable
AS       Child
JOIN     JoinTable
      ON Child.ID = JoinTable.ID
LEFT JOIN MainTable
AS       Parent
      ON Parent.ID = JoinTable.ParentID
     AND Parent.SomeOtherData = Child.SomeOtherData
WHERE    ...[some info to make sure we don't select parent records in the child dataset]...

INNER JOINLEFT JOINの結果の違いを理解しました。この場合、すべての子が親を持つのとまったく同じ結果を返します。両方のクエリを実行すると、データセットを比較できますが、まったく同じです。

LEFT JOININNER JOINよりもはるかに高速に実行されるのはなぜですか?


UPDATEクエリプランをチェックし、内部結合を使用する場合は、親データセットから開始します。左結合を行う場合、子データセットから始まります。

使用するインデックスはすべて同じです。

常に子供から始めるように強制できますか?左結合を使用するとうまくいきます。


以前に同様の質問がここで行われましたが、私の質問に答える人はいないようです。

例えば SQL ServerでのINNER JOINとLEFT JOINのパフォーマンス で選択した答えは、左結合は常に内部結合よりも遅いことを示しています。議論は理にかなっていますが、私が見ているものではありません。

22
Greg

SQLは最初に小さい選択を実行してから、この小さいレコードセットに結合するように強制されるため、左結合はより高速に見える。何らかの理由で、オプティマイザはこれを自然に実行したくありません。

結合を正しい順序で強制する3つの方法:

  1. データの最初のサブセットを選択して一時テーブル(またはテーブル変数)に入れ、結合します
  2. 左結合を使用します(これは内部結合ではなく左結合であるため、これは異なるデータを返す可能性があることに注意してください)
  3. fORCE ORDERキーワードを使用します。テーブルサイズまたはスキーマが変更された場合、クエリプランが正しくない可能性があることに注意してください( https://dba.stackexchange.com/questions/45388/forcing-join-order を参照)
13
Greg

これを試してみてください。同じ結果、異なるアプローチ:

SELECT c.ID, p.ID 
FROM
(SELECT   Child.ID, JoinTable.ParentID
FROM     MainTable
AS       Child
JOIN     JoinTable
      ON Child.ID = JoinTable.ID) AS c
INNER JOIN 
(SELECT   Parent.ID, JoinTable.ID
FROM     MainTable
AS       Parent
JOIN     JoinTable
      ON Parent.ID = JoinTable.ParentID
     AND Parent.SomeOtherData = Child.SomeOtherData) AS p
ON c.ParentID = p.ID

問題が解決しない場合は、cteを使用します。

;WITH cte AS
(SELECT   Child.ID, JoinTable.ParentID
FROM     MainTable
AS       Child
JOIN     JoinTable
      ON Child.ID = JoinTable.ID)
SELECT cte.ID, Parent.ID
FROM cte INNER JOIN 
MainTable
AS       Parent
      ON Parent.ID = cte.ParentID
     AND Parent.SomeOtherData = cte.SomeOtherData
2
cha