web-dev-qa-db-ja.com

クエリを高速化する方法

私のクエリは約590行と8列を返します。私が抱えている問題は、クエリが最初から最後まで2分30秒で完了することです。ここにいるすばらしい人々のグループは、より効率的なクエリの書き方についてたくさん教えてくれたので、ここにもう1つあります!

私の変数には日付のみが含まれているため、date変数を使用し、datetimeは使用していません。また、提案されているように、日付をyyyymmdd形式で保存しています。 Aaron Bertrand-Bad Habits To Kick による。

このクエリを最適化して結果をより速く返すためにできることはありますか?

DECLARE @Startdate date = '20170101', 
        @Enddate   date = '20170131';

WITH fc As
(
    Select
    Teacher
    ,Team
    ,fc
    FROM [Helper].[dbo].[fc]
)
,ia As
(
    Select
    Teacher
    ,tia
    FROM dbo.ia
    WHERE [hiredate] >= @Startdate
    AND [hiredate] <  DATEADD(DAY,1,@Enddate)
)
,inb As
(
    Select 
    Teacher
    ,tinb
    FROM inb
),
ripcord As
(
    Select
    Teacher
    ,pit
    FROM [homebase].[dbo].[rip]
)
,YRTR As
(
    Select
    Teacher
    ,totamt
    FROM totamt
    WHERE CAST([begindate] As DATE) BETWEEN CAST(DateAdd(yy, -1, @startdate) As Date) 
                                     AND CAST(DateAdd(yy, -1, @enddate) As Date)
)
Select 
DISTINCT rost.[Teacher] As Teacher
,[Team Name] = fc.team
,[TIA] = ROUND(SUM(ISNULL(ia.tia,0)),0)
,[SUM] = CAST(ROUND(SUM(ISNULL(inb.tinb,0))+SUM(ISNULL(rip.pit,0)),0) As INT)
,[YR] = ROUND(SUM(ISNULL(tr.totamt,0)),0)
,[fc] = ISNULL(fc,0)
,[1st Count] = COALESCE(b.[students],0)
,[2nd Count] = COALESCE(c.[potstudents],0)
FROM dbo.roster rost
LEFT JOIN fc fc
ON rost.Teacher = fc.Teacher
LEFT JOIN ia ia
ON ia.Teacher = rost.Teacher
LEFT JOIN inb inb
ON rost.Teacher = inb.Teacher
LEFT JOIN ripcord rip
ON rost.Teacher = rip.Teacher
LEFT JOIN YRTR tr
ON rost.Teacher = tr.Teacher
OUTER APPLY
( SELECT 
        DISTINCT Teacher [Teacher]
        , COUNT(students) AS students
    FROM students
    WHERE Teacher = rost.Teacher
    AND regdate >= @Startdate
    AND regdate <  DATEADD(DAY,1,@Enddate)
    GROUP BY Teacher
) AS b
OUTER APPLY
(
    Select
    DISTINCT Teacher Teacher
    ,COUNT(potstudents) As potstudents
    FROM dbo.potstudents
    WHERE Teacher = rost.Teacher
    AND returndate >= @Startdate
    AND returndate <  DATEADD(DAY,1,@Enddate)
    GROUP BY Teacher
) AS c
GROUP BY rost.[Teacher],fc.TEAM, fc.fc,b.[students],c.[potstudents]
ORDER BY rost.[Teacher] ASC

[〜#〜]編集[〜#〜]
(リンクを忘れてしまい、申し訳ありません)@sabin bioのリクエストで-これは Query Execution Plan へのリンクです

また、インデックスを持たないCTEのクエリビュー。

まず、クエリオプティマイザーがクエリによって生成したプランを理解します。あなたの質問では、クエリは約590行を返しますが、含まれているクエリプランは18行しか返しません。正しいプランを添付しましたか?とにかく歩きます。右から左に読む:

rosterのテーブル全体をスキャンして、18行を取得します。この結果セットは、次のすべてのネストされたループ結合の外側部分として使用されます。外部結果セットの各行について:

  • iaテーブルをフルスキャンします
  • inbテーブルをフルスキャンします
  • ripcordテーブルをフルスキャンします
  • YRTRテーブルをフルスキャンします
  • studentsテーブルをフルスキャンします
  • potstudentsテーブルをフルスキャンします

ある時点で、行数は最大19に増加します。_GROUP BY_は、ノードIDが4のSORTとして実装されます。DISTINCTは、Sort (Distinct Sort)、ノードIDは0。

プログラマはクエリプランを直接制御することはできませんが、それに影響を与えるためにできることはたくさんあります。クエリの実行時間が遅すぎるとのことですが、上記の計画はあなたにとって効率的に聞こえますか?クエリオプティマイザのアクションを選択できるとしたらどうでしょうか。 18行を返すためだけに約140のテーブルスキャンを実行しています。非常に少数の行の場合、これは問題ないかもしれませんが、本番環境でより多くのデータがあるようです。

クエリを最適化する1つの方法は、そのクエリのIO要件を減らすことです。ここでは、実装が簡単になるように多くのテーブルスキャンを実行しています。クエリオプティマイザが取得できるようにインデックスを作成します。関連データをより効率的に使用します。ビューを参照している場合でも、ビューで使用されるテーブルにインデックスを作成してパフォーマンスを向上させることができます。例を示すために、fcに対するテーブルアクセスの述語を次に示します。

_[Test].[dbo].[roster].[Teacher] as [rost].[Teacher]=[Test].[dbo].[fc].[Teacher]
_

Teacherfc列にインデックスを作成すると、そのテーブルに対して異なる、場合によってはより効率的なテーブルアクセスメソッドが作成される可能性があります。オプションで、Teacherテーブルで使用されるすべての列をINCLUDE列として追加できます。

コードだけを見ると、クエリでローカル変数を使用することの影響に注意する必要があります。クエリオプティマイザーは、クエリプランの作成時にこれらのローカル変数の値を認識しません。ハードコードされたルールに基づいて、デフォルトのカーディナリティの推定を行います。一部のクエリでは、OPTION (RECOMPILE)ヒントを追加するか、ローカル変数をハードコードされた値に置き換えると、クエリオプティマイザーがプランの作成前に変数の値を知っているため、クエリプランが大幅に改善されます。

また、DISTINCTと_GROUP BY_を無計画にクエリに追加しないでください。クエリオプティマイザーがそれらの一部を最適化したように見えますが、ほとんどの場合、同じクエリに_GROUP BY_とDISTINCTの両方がある場合、何か間違っています。このクエリを変更すると、パフォーマンスにどの程度の影響があるかは明らかではありません。

4
Joe Obbish

distinctの不要な使用を中止してください。外側の適用サブクエリだけでなく、メインクエリ自体。

もう1つ試すことができるのは、外側の適用サブクエリを削除することです。代わりに、テーブルと一致条件を_left joins_として追加します。メインクエリで、count(distinct student pk)に変更します。

他にもいくつか試すことができますが、私はそれらの外側の適用に焦点を当てるか、少なくとも実行計画を見て、どこに焦点を当てるかを考えます。

チェックするもう1つの重要なことは、テーブルに適切なインデックスがあることです。結果の数を考慮すると、インデックスが欠落している可能性があります。

あなたの計画にはたくさんのテーブルスキャンがあることに気づきました。頻繁に結合している列にインデックスを追加することを検討してください。一般的にフィルターをかける場合は、これらの日付にも追加してください。

基になるテーブルを変更する必要があります。インデックス付きビューを作成できますが、これには多くの制限があり、インデックステーブルの代わりにはなりません。ビューにインデックスを付けることは、パフォーマンスの負担をselects(読み取り)データからinserts、updates、およびdeletes(書き込み)に移動する良い方法です。ディスク容量を犠牲にしてデータ。驚くべきパフォーマンス上の利点がありますが、コストはかかりません。

Joe Obbishの回答 はクエリプランに対してより完全な応答を示します。

1
Arin Taylor

CTEは不要ですselectなしでwhere
表を直接使用するだけ

Select [rost].[Teacher] As Teacher
     , [Team Name] = fc.team 
     , [fc] = ISNULL(fc, 0) 

     , [1st Count] = count(b.[students])
     , [2nd Count] = count(c.potstudents)
     , [TIA] = ROUND(SUM(ISNULL(ia.tia, 0)), 0)
     , [SUM] = CAST(ROUND(SUM(ISNULL(inb.tinb, 0)) + SUM(ISNULL(rip.pit, 0)), 0) As INT)
     , [YR]  = ROUND(SUM(ISNULL(tr.totamt, 0)), 0)    
FROM dbo.roster rost
LEFT JOIN fc 
       ON fc.Teacher  = rost.Teacher 
LEFT JOIN ia
       ON ia.Teacher  = rost.Teacher
LEFT JOIN inb inb
       ON inb.Teacher = rost.Teacher
LEFT JOIN ripcord rip
      ON  rip.Teacher = rost.Teacher
LEFT JOIN YRTR tr
       ON tr.Teacher  = rost.Teacher
LEFT JOIN students b
      on b.Teacher    = rost.Teacher
     AND b.regdate >= @Startdate
     AND b.regdate <  DATEADD(DAY,1,@Enddate)
LEFT JOIN dbo.potstudents c
      ON c.Teacher    = rost.Teacher
     AND c.returndate >= @Startdate
     AND c.returndate <  DATEADD(DAY,1,@Enddate)
GROUP BY rost.[Teacher], fc.TEAM, fc.fc 
ORDER BY rost.[Teacher] ASC
1
paparazzo