web-dev-qa-db-ja.com

SQL Serverが修飾するテーブルの各行に対してサブクエリを実行するのはなぜですか?

このクエリは〜21秒で実行されます( 実行計画 ):

select 
    a.month
    , count(*) 
from SubqueryTest a 
where a.year = (select max(b.year) from SubqueryTest b)
group by a.month

サブクエリが変数に置き換えられると、1秒未満で実行されます( 実行計画 )。

declare @year float
select @year = max(b.year) from SubqueryTest b
select 
    month
    , count(*) 
from SubqueryTest where year = @year group by month

実行計画から判断すると、「select max ...」副選択が「SubqueryTest a:」の数百万行ごとに実行されるため、非常に時間がかかります。

私の質問:サブセレクトはスカラーで確定的で相関がないため、クエリオプティマイザーが2番目の例で行った処理を実行してサブクエリを1回実行し、結果を格納してからメインクエリに使用しないのはなぜですか? SQL Serverの理解に問題があるだけだと思いますが、それを埋める手助けをしたいのですが、Googleでの数時間は役に立ちませんでした。

テーブルは1 GBをわずかに超え、約2800万のレコードがあります。

CREATE TABLE SubqueryTest(
  [pk_id] [int] IDENTITY(1,1) NOT NULL
  , [Year] [float] NULL
  , [Month] [float] NULL PRIMARY KEY CLUSTERED ([pk_id] ASC))

CREATE NONCLUSTERED INDEX idxSubqueryTest ON SubqueryTest ([Year] ASC)
7
CaptainSlock

遅い計画ではない外部クエリの各行のMAXを計算します。

実際、それを明示的に計算することは決してありません。

それはに似た計画を与えます

WITH CTE
     AS (SELECT TOP(1) WITH TIES *
         FROM   SubqueryTest
         WHERE year IS NOT NULL
         ORDER  BY year desc)
SELECT month,
       count(*)
FROM   CTE
GROUP  BY month 

スロープラン(推定行数)

enter image description here

year ascに非カバーインデックスがあるため、それを逆方向にスキャンして、最初の年の行を取得します(暗黙のIS NOT NULL述語のため、シークとして表示されます)。

残念ながら、行数を推定する場合、TOP 1TOP 1 WITH TIESは区別されないようです。

この場合、それは大きな違いを生みます。 (推定2キールックアップ対実際の4,424,803)したがって、不適切な計画を取得します。

スロープラン(実際の行数)

enter image description here

monthyearのインデックスにキーまたはインクルード列として追加して、インデックスをカバーすることを検討できます。セカンダリキー列として追加することの利点は、追加の並べ替えなしでストリーム集約にフィードできることです(とにかく、ハッシュ集約は12個の異なる値で十分です)。

このような非選択的な列の非カバリングインデックスは、クエリの大部分では実際にはほとんど役に立ちません。インデックスは、テーブル全体で並列スキャンを実行し、27,445,400行すべての述語を評価する(膨大な数の検索を実行するよりも優先される)「高速」プランでは完全に無視されます。

enter image description here

6
Martin Smith