2011-09-20 11 views
14

これらのクエリには大きな違いがあります。SQLはなぜSELECT COUNT(*)、MIN(col)、MAX(col)の方が速いのですか?SELECT MIN(col)、MAX(col)

スロークエリ

SELECT MIN(col) AS Firstdate, MAX(col) AS Lastdate 
FROM table WHERE status = 'OK' AND fk = 4193 

テーブル 'テーブル'。スキャンカウント2、論理読み取り2458969、物理読み取り0、先読み読み取り0、論理読み取り0、論理読み取り0、論理読み取り0、読み取り先読み0のロブ。

SQL Server実行時間:CPU時間= 1966 ms 、経過時間= 1955ms。

速いクエリ

SELECT count(*), MIN(col) AS Firstdate, MAX(col) AS Lastdate 
FROM table WHERE status = 'OK' AND fk = 4193 

テーブル 'テーブル'。スキャンカウント1、論理読み取り5803、物理読み取り0、先読み読み取り0、論理読み取り0、論理読み取り0、論理読み取り0、読み取り先読みLOB 0

SQL Server実行時間:CPU時間= 0 ms 、経過時間= 9ms。

質問

クエリの間に大きなパフォーマンスの差の理由は何ですか?

更新 コメントとして与えられた質問に基づいて、少し更新:

実行または繰り返し実行の順序は、賢明な何も性能が変化します。 追加のパラメータは使用されておらず、(テスト)データベースは実行中に何も実行していません。

スロークエリ

|--Nested Loops(Inner Join) 
|--Stream Aggregate(DEFINE:([Expr1003]=MIN([DBTest].[dbo].[table].[startdate]))) 
    | |--Top(TOP EXPRESSION:((1))) 
    |   |--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1008]) WITH ORDERED PREFETCH) 
    |    |--Index Scan(OBJECT:([DBTest].[dbo].[table].[startdate]), ORDERED FORWARD) 
    |    |--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[FK]=(5806) AND [DBTest].[dbo].[table].[status]<>'A') LOOKUP ORDERED FORWARD) 
    |--Stream Aggregate(DEFINE:([Expr1004]=MAX([DBTest].[dbo].[table].[startdate]))) 
     |--Top(TOP EXPRESSION:((1))) 
      |--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1009]) WITH ORDERED PREFETCH) 
        |--Index Scan(OBJECT:([DBTest].[dbo].[table].[startdate]), ORDERED BACKWARD) 
        |--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[FK]=(5806) AND [DBTest].[dbo].[table].[status]<>'A') LOOKUP ORDERED FORWARD) 

高速クエリ

|--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1012],0))) 
    |--Stream Aggregate(DEFINE:([Expr1012]=Count(*), [Expr1004]=MIN([DBTest].[dbo].[table].[startdate]), [Expr1005]=MAX([DBTest].[dbo].[table].[startdate]))) 
     |--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1011]) WITH UNORDERED PREFETCH) 
      |--Index Seek(OBJECT:([DBTest].[dbo].[table].[FK]), SEEK:([DBTest].[dbo].[table].[FK]=(5806)) ORDERED FORWARD) 
      |--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[status]<'A' OR [DBTest].[dbo].[table].[status]>'A') LOOKUP ORDERED FORWARD) 

The execution plan from SSMS

回答

は下記の答えMartin Smithはこの問題を説明しているようです。極短いバージョンは、MS-SQLクエリアナライザが完全なテーブルスキャンを引き起こす低速クエリでクエリプランを間違って使用することです。

カウント(*)を追加すると、(FORCESCAN)のクエリヒントまたは開始日、FKおよびステータス列の結合インデックスによって、パフォーマンスの問題が修正されます。

+2

2番目のクエリの後に1番目のクエリを実行するとどうなりますか? – gbn

+1

カウント(*)を使用している場合、fk = 4193のすべてのレコードをチェックしない可能性がありますか? – nosbor

+1

これを実行していますか?もしそうなら、両方のクエリの前に 'DBCC DROPCLEANBUFFERS'と' DBCC FREEPROCCACHE'を置くとどうなりますか?シーケンスを変更するとどうなりますか?最初に高速クエリを実行した後、遅いクエリを実行します。 –

答えて

24

SQL Serverのカーディナリティの推定は、このような

  • インディペンなど様々なモデリングのことを前提とします。相関情報が利用可能でない限り、異なる列のデータ分布が独立しています。
  • 均一性:各統計オブジェクトヒストグラムステップ内で、個別の値が均等に分散され、各値は同じ頻度を持ちます。

Source

テーブル内の810064行があります。

あなたは、クエリ

SELECT COUNT(*), 
     MIN(startdate) AS Firstdate, 
     MAX(startdate) AS Lastdate 
FROM table 
WHERE status <> 'A' 
     AND fk = 4193 

1893を持っている(0.23%)の行はfk = 4193述語を満たし、かつそれら二つのstatus <> 'A'一部ので、全体の1891試合を失敗し、集約する必要があります。

また、クエリ全体をカバーする2つのインデックスもありません。あなたの高速クエリのために

それが直接fk = 4193が、その後status述語をチェックして、集約のためのstartdateを取得するために、クラスタ化インデックス内の各行を見つけるために、1893 key lookupsを行う必要がある行を見つけるために、fkにインデックスを使用しています。

あなたはSQL Serverがもはやは、すべての条件を満たす行を処理するためにを持ってSELECTリストからCOUNT(*)を削除します。その結果、別のオプションが検討されます。

あなたはMAXでき同様に初めから、それは最初に一致した行の停止を見つけるとバックベーステーブルへとすぐにキー検索を行うことがMIN(startdate)を発見したように、それはスキャンを開始することができstartdateにインデックスを持っていますインデックスのもう一方の端を開始し、逆方向に作業する別のスキャンで検出されます。

SQL Serverは、これらのスキャンのそれぞれが、述部と一致するものにヒットする前に590行の処理を終了すると推定しています。総計1,180件の検索が1,893件であるため、この計画を選択します。

590数字はちょうどtable_size/estimated_number_of_rows_that_matchです。すなわち、基数推定器は、一致する行がテーブル全体に均等に分布すると仮定する。

残念ながら、述語を満たす1,891行は、startdateに関してはではなく、です。実際、それらは全て、インデックスの最後に向かって8,205行の単一のセグメントに集約され、MIN(startdate)に到達するスキャンは、停止する前に801,859のキールックアップを終了することを意味する。

これは以下のとおりです。あなたは、クエリを使用して検討することもでき

CREATE TABLE T 
(
id int identity(1,1) primary key, 
startdate datetime, 
fk int, 
[status] char(1), 
Filler char(2000) 
) 

CREATE NONCLUSTERED INDEX ix ON T(startdate) 

INSERT INTO T 
SELECT TOP 810064 Getdate() - 1, 
        4192, 
        'B', 
        '' 
FROM sys.all_columns c1, 
     sys.all_columns c2 


UPDATE T 
SET fk = 4193, startdate = GETDATE() 
WHERE id BETWEEN 801859 and 803748 or id = 810064 

UPDATE T 
SET startdate = GETDATE() + 1 
WHERE id > 810064 


/*Both queries give the same plan. 
UPDATE STATISTICS T WITH FULLSCAN 
makes no difference*/ 

SELECT MIN(startdate) AS Firstdate, 
     MAX(startdate) AS Lastdate 
FROM T 
WHERE status <> 'A' AND fk = 4192 


SELECT MIN(startdate) AS Firstdate, 
     MAX(startdate) AS Lastdate 
FROM T 
WHERE status <> 'A' AND fk = 4193 

fkではなくstartdateにインデックスを使用するか、または提案欠落インデックスがこの問題を回避するために(fk,status) INCLUDE (startdate)に実行計画で強調表示を追加する計画を強制するヒント。

+0

クエリからステータス列を削除すると、両方のクエリが数%遅くなります。 – CodingBarfield

+0

自動作成統計/自動更新統計情報 – CodingBarfield

+0

実行計画XML http://pastebin.com/mBcgHYkN 保守計画が実行不能であったかどうかを確認しています。 – CodingBarfield

関連する問題