JOIN句でORを使用する場合の奇妙なクエリプラン-テーブル内のすべての行の定数スキャン


10

JOIN句でORを使用するよりも2つの結果セットのUNIONが優れている理由を示すために、サンプルクエリプランを作成しようとしています。私が書いたクエリプランには困惑しています。Users.Reputationの非クラスター化インデックスでStackOverflowデータベースを使用しています。

クエリプランイメージ クエリは

CREATE NONCLUSTERED INDEX IX_NC_REPUTATION ON dbo.USERS(Reputation)
SELECT DISTINCT Users.Id
FROM dbo.Users
INNER JOIN dbo.Posts  
    ON Users.Id = Posts.OwnerUserId
    OR Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5

クエリプランはhttps://www.brentozar.com/pastetheplan/?id=BkpZU1MZEにあります。クエリの所要時間は4:37分で、26612行が返されました。

このスタイルの定数スキャンが既存のテーブルから作成されるのを見たことがありません-常に一定のスキャンがユーザーによって入力された単一の行に対して使用される場合、すべての単一の行に対して一定のスキャンが実行される理由がわかりませんたとえば、SELECT GETDATE()。なぜここで使用されるのですか?このクエリプランを読む際に、いくつかのガイダンスをいただければ幸いです。

そのORをUNIONに分割すると、同じ26612行が返される12秒で実行される標準プランが作成されます。

SELECT Users.Id
FROM dbo.Users
    INNER JOIN dbo.Posts
       ON Users.Id = Posts.OwnerUserId
WHERE Users.Reputation = 5
UNION 
SELECT Users.Id
FROM dbo.Users
    INNER JOIN dbo.Posts
       ON  Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5

この計画を次のように解釈します。

  • 投稿からすべての41782500行を取得します(実際の行数は投稿のCIスキャンと一致します)
  • 投稿の各41782500行について:
    • スカラーを生成します。
    • Expr1005:OwnerUserId
    • Expr1006:OwnerUserId
    • Expr1004:静的な値62
    • Expr1008:LastEditorUserId
    • Expr1009:LastEditorUserId
    • Expr1007:静的な値62
  • 連結では:
    • Exp1010:Expr1005(OwnerUserId)がnullでない場合は、Expr1008(LastEditorUserID)を使用します。
    • Expr1011:Expr1006(OwnerUserId)がnullでない場合はそれを使用し、そうでない場合はExpr1009(LastEditorUserId)を使用します
    • Expr1012:Expr1004(62)がnullの場合はそれを使用し、そうでない場合はExpr1007(62)を使用します
  • Computeスカラー:アンパサンドが何をするのかわかりません。
    • Expr1013:4 [および?] 62(Expr1012)= 4およびOwnerUserId IS NULL(NULL = Expr1010)
    • Expr1014:4 [および?] 62(Expr1012)
    • Expr1015:16および62(Expr1012)
  • 並べ替え順:
    • Expr1013 Desc
    • Expr1014 Asc
    • Expr1010 Asc
    • Expr1015 Desc
  • マージ間隔で、Expr1013とExpr1015を削除しました(これらは入力ですが、出力ではありません)
  • ネストされたループ結合の下のインデックスシークでは、Expr1010およびExpr1011をシーク述語として使用していますが、IX_NC_REPUTATIONからExpr1010およびExpr1011を含むサブツリーへのネストされたループ結合を実行していない場合、これらにアクセスする方法がわかりません。
  • ネストされたループの結合は、以前のサブツリーで一致するUsers.IDのみを返します。述部プッシュダウンのため、IX_NC_REPUTATIONのインデックスシークから返されたすべての行が返されます。
  • 最後のネストループ結合:各投稿レコードについて、以下のデータセットで一致が見つかったUsers.Idを出力します。

EXISTSサブクエリまたはサブクエリを試しましたか?SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND ( EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.OwnerUserId) OR EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.LastEditorUserId) ) ;
ypercubeᵀᴹ

1つのサブクエリ:SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id IN (Posts.OwnerUserId, Posts.LastEditorUserId) ) ;
ypercubeᵀᴹ

回答:


10

この計画は、ここで詳しく説明する計画と似ています

Postsテーブルがスキャンされます。

行ごとにOwnerUserIdandを抽出しLastEditorUserIdます。これは、動作方法と同様の方法UNPIVOTです。以下のプランでは、各入力行に対して2つの出力行を作成するための単一の定数スキャン演算子が表示されます。

SELECT *
FROM dbo.Posts
UNPIVOT (X FOR U IN (OwnerUserId,LastEditorUserId)) Unpvt

この場合、or両方の列の値が同じである場合、1つだけの行がUsers(2 つではなく)の結合から出力される必要があるため、計画はもう少し複雑です。

次に、これらはマージ間隔に置かれるため、値が同じ場合、範囲は縮小され、1回のシークのみUsersが実行されます。それ以外の場合は、2回のシークが実行されます。

62は、シークが平等シークであることを意味するフラグです。

に関して

IX_NC_REPUTATIONからExpr1010およびExpr1011を含むサブツリーへのネストされたループ結合を行っていない場合、これらにアクセスする方法がわかりません

これらは、黄色で強調表示された連結演算子で定義されます。これは、黄色でハイライトされたネストされたループの外側にあります。そのため、これは、ネストされたループの内側で強調表示されている黄色のシークの前に実行されます。

ここに画像の説明を入力してください

これに役立つ場合に備えて、同様のプランを提供する書き直し(マージ間隔をマージユニオンに置き換えました)を以下に示します。

SELECT DISTINCT D2.UserId
FROM   dbo.Posts p
       CROSS APPLY (SELECT Users.Id AS UserId
                    FROM   (SELECT p.OwnerUserId
                            UNION /*collapse duplicate to single row*/
                            SELECT p.LastEditorUserId) D1(UserId)
                           JOIN Users
                             ON Users.Id = D1.UserId) D2
OPTION (FORCE ORDER) 

ここに画像の説明を入力してください

Postsテーブルで使用可能なインデックスに応じて、このクエリのバリアントは、提案されたUNION ALLソリューションよりも効率的な場合があります。(私が持っているデータベースのコピーには、これに役立つインデックスがありませんPosts。提案されたソリューションは、2回の完全スキャンを実行します。以下は、1回のスキャンで実行します)

WITH Unpivoted AS
(
SELECT UserId
FROM dbo.Posts
UNPIVOT (UserId FOR U IN (OwnerUserId,LastEditorUserId)) Unpivoted
)
SELECT DISTINCT Users.Id
FROM dbo.Users INNER HASH JOIN Unpivoted
       ON  Users.Id = Unpivoted.UserId
WHERE Users.Reputation = 5

ここに画像の説明を入力してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.