web-dev-qa-db-ja.com

SQL Serverで「select *」が「select top 500 *」よりも速いのはなぜですか?

私にはcomplicated_viewというビューがあります-いくつかの結合とwhere句があります。さて、

select * from complicated_view (9000 records)

より速いはるかに速いよりも

select top 500 * from complicated_view

19秒と5分以上の話です。

最初のクエリはすべての9000レコードを返します。トップ500を途方もなく長く取得しているだけですか?

明らかに、ここで実行計画を見ていきます-----しかし、私が理解したらwhy SQL Serverが「トップ500」を最適ではない方法で実行しているのですが、実際にどのように伝えるか完全なテーブルを取るように、計画をすばやく実行しますか?

もちろん、私はビューを完全に書き直さなければならないかもしれません---しかし、かなり奇妙です。

基本的に、私はこのデータテーブルを、変更できないデフォルトのselect top 500 *クエリでテーブルを事前チェックするサードパーティソフトウェアに接続しています。したがって、このビューを実際のテーブルにダンプする(かなりずさんな)以外に、 "上位500"の補遺も回避できません。

これはSQL Server 2012です。

編集:重複フラグに同意しません。他の質問、トップはすべてよりも速かった。これは期待される動作であり、返される行が少なくなります。私の場合は反対です。また、私の理解では、Top 100はTop 100+とは異なるアルゴリズムです。重複する質問に正解があるとは思いません。つまり、TOP Xクエリは非常に早い段階で潜在的に大量のテーブルをSORTします。それらが集計/フィルター処理された後などではありません。理由は謎ですが、その方法は明白です。

14
user45867

クエリにTOP句を追加すると、クエリに 行の目標 が導入されます。クエリオプティマイザーは、より効率的なクエリプランを作成するためにすべての行を返す必要がないという事実を利用しようとします。行の目標により、一部のオペレーターのコストが削減される可能性があります。行の目標の最適化は、モデルの制限または統計オブジェクトの不完全な情報が原因で、クエリチューナーの支持に反して機能する可能性があります。以下に、_TOP 500_を追加するとパフォーマンスが低下する単純なビューに対するデモがあります。

まず、奇数の整数のみをテーブルに挿入します。最後に完全な統計を収集していることに注意してください。

_DROP TABLE IF EXISTS dbo.ODD;

CREATE TABLE dbo.ODD (
    ID BIGINT NOT NULL,
    FLUFF VARCHAR(10)
);

INSERT INTO dbo.ODD WITH (TABLOCK)
SELECT TOP (100000)
  -1 + 2 * ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, REPLICATE('FLUFF', 2)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

CREATE STATISTICS S ON dbo.ODD (ID) WITH FULLSCAN;
_

次に、偶数の整数のみを別のテーブルに挿入します。デモを機能させるために、値と行サイズを繰り返していくつかのことを行っています。私はまだ最後に完全に統計を更新します。

_DROP TABLE IF EXISTS dbo.EVEN;

CREATE TABLE dbo.EVEN (
    ID BIGINT NOT NULL,
    FLUFF VARCHAR(3500)
);

INSERT INTO dbo.EVEN WITH (TABLOCK)
SELECT TOP (100000) 
  1000 * FLOOR ( ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) / 500)
, REPLICATE('FLUFF', 700)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2

CREATE STATISTICS S ON dbo.EVEN (ID) WITH FULLSCAN;
_

これがビューの定義です:

_CREATE OR ALTER VIEW dbo.TRICKY_VIEW AS
SELECT o.ID
FROM dbo.ODD o
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.EVEN e WHERE o.ID = e.ID
);
_

次のクエリについて考えてみます。

_SELECT TOP 500 *
FROM dbo.TRICKY_VIEW
OPTION (MAXDOP 1);
_

クエリプランは次のようになります。

bad plan

コストの制限 ネストされたループ結合の内側にあるEVENテーブルのフルスキャンでは、相対コストが低くなります。最初の500行をクライアントに返すために、オプティマイザがEVENテーブルから500 * 100000 = 5,000万行をスキャンする必要があることがわかっているので、データの構築方法に基づいています。これは実際に何が起こるかであり、クエリは私のマシンで execute になるまで約16秒かかります。

bad actual plan

クエリからTOP句を削除すると、別のより効率的な plan が得られます。

good plan

このクエリは、私のマシンで0.5秒未満で実行されます。 EVENテーブルから読み取られるのは100000行だけです。

SQL Server 2016以降のバージョンでは、OPTION (USE HINT('DISABLE_OPTIMIZER_ROWGOAL'))をクエリに追加することで、ビューの定義を変更せずにこの問題を回避できます。このヒントは、クエリレベルでの行の目標の最適化を無効にします。 SQL Server 2012では、OPTION (QUERYTRACEON 4138)を介してクエリレベルでトレースフラグ4138を使用できますが、SAが必要です。

クエリプランを確認しないと、クエリについては特に何も言えませんが、この例が一般的なポイントを示していることを願っています。

22
Joe Obbish