web-dev-qa-db-ja.com

実行プランにソートが表示されるのはなぜですか?

以下のSQLクエリの実行速度が非常に遅いです。実行プランを調べたところ、Files.OrderIdでの並べ替えが最もコストの高い操作(53%)であると主張しています。 OrderIdで注文していない場合、なぜこれが発生するのでしょうか。 File.OrderIdにインデックスを作成するのが最善の策ですか?

実行計画 誰かが興味を持っている場合。

with custOrders as
(
    SELECT c.firstName + ' ' + c.lastname as Customer, c.PartnerId , c.CustomerId,o.OrderId,o.CreateDate, c.IsPrimary
    FROM Customers c
    LEFT JOIN CustomerRelationships as cr
        ON c.CustomerId = cr.PrimaryCustomerId
    INNER JOIN Orders as o
       ON c.customerid = o.customerid 
           OR (cr.secondarycustomerid IS NOT NULL AND o.customerid = cr.secondarycustomerid)
    where c.createdate >= @FromDate + ' 00:00' 
       AND c.createdate <= @ToDate + ' 23:59' 
),
 temp as
(
SELECT Row_number() 
         OVER ( 
           ORDER BY c.createdate DESC)                    AS 'row_number', 
       c.customerid as customerId, 
       c.partnerid as partnerId, 
       c.Customer, 
       c.orderid as OrderId, 
       c.createdate as CreateDate, 
       Count(f.orderid)                                   AS FileCount, 
       dbo.Getparentcustomerid(c.isprimary, c.customerid) AS ParentCustomerId, 
       au.firstname + ' ' + au.lastname                   AS Admin, 
       '' as blank, 
       0  as zero
FROM   custOrders c 
       INNER JOIN files f 
               ON c.orderid = f.orderid 
       INNER JOIN admincustomers ac 
               ON c.customerid = ac.customerid 
       INNER JOIN adminusers au 
               ON ac.adminuserid = au.id 
       INNER JOIN filestatuses s 
               ON f.statusid = s.statusid 
WHERE  ac.adminuserid IS NOT NULL 
       AND f.statusid NOT IN ( 5, 6 ) 
GROUP  BY c.customerid, 
          c.partnerid, 
          c.Customer, 
          c.isprimary, 
          c.orderid, 
          c.createdate, 
          au.firstname, 
          au.lastname 
)
20
Abe Miessler

SQL Serverには、2つのテーブルを結合する必要がある場合に選択できる3つのアルゴリズムがあります。 Nested-Loops-Join、Hash-Join、Sort-Merge-Join。どちらを選択するかは、コスト見積もりに基づいています。この場合、利用可能な情報に基づいて、Sort-Merge-Joinが正しい選択であることがわかりました。

SQL Serverの実行プランでは、Sort-MergeはSortとMerge-Joinの2つの演算子に分割されます。これは、データが既に並べ替えられている場合など、並べ替え操作が不要な場合があるためです。

結合に関する詳細については、ここで私の結合シリーズをチェックしてください: http://sqlity.net/en/1146/a-join-a-day-introduction/ Sort-Merg-Joinに関する記事ここにあります: http://sqlity.net/en/1480/a-join-a-day-the-sort-merge-join/


クエリを高速化するために、最初にインデックスを調べます。クエリには多数のクラスター化インデックススキャンがあります。それらのいくつかをシークに置き換えることができれば、おそらくより良いでしょう。また、SQL Serverが生成する見積もりが、実際の実行プランの実際の行数と一致するかどうかを確認します。それらが遠く離れている場合、SQLServerはしばしば悪い選択をします。したがって、より良い統計を提供すると、パフォーマンスのクエリにも役立ちます。

12
Sebastian Meine

SQL Serverは、並べ替えを実行して、その並べ替え演算子の右側にあるデータセットとOrdersテーブルのレコード間のマージ結合を有効にします。マージ結合自体は、データセット内のすべてのレコードを結合する非常に効率的な方法ですが、結合する各データセットを結合キーに従って同じ順序で並べ替える必要があります。

PK_OrdersキーはすでにOrderIDによって順序付けられているため、SQL Serverは、結合のもう一方の端(並べ替えの右側にある他のもの)を並べ替えて、この2つを利用することにしました。データセットは、プランのその時点でマージできます。マージ結合の一般的な代替手段はハッシュ結合ですが、ソートとマージの代わりに高価なハッシュ結合演算子を使用するため、これは役に立ちません。この場合、クエリオプティマイザは、並べ替えとマージがより効率的であると判断しました。

計画のコストのかかるステップの根本的な原因は、注文テーブルのすべてのレコードをデータセットに結合する必要があることです。 filesテーブルからのレコードを制限する方法はありますか? files.statusidのインデックスは、5,6にないレコードがテーブルの合計サイズの10%未満である場合に役立つことがあります。

QOは、ほとんどのレコードが最後に除外されると考えています。計画の途中で処理する必要のあるレコードが少なくなるように、これらのフィルター条件をできるだけ多くレコードソースにプッシュして戻します。

編集:言及するのを忘れました。私たちが見ることができる実行計画を持っていることは非常に役に立ちます。 actual実行プランの結果を取得して、これらの演算子を通過するレコードの実際の数を確認する方法はありますか?推定レコード数が少しずれている場合があります。

EDIT:最後から2番目のフィルター演算子の述語フィールドを詳しく調べて要約します。

c.CustomerId=o.CustomerId
OR o.CustomerId=cr.SecondaryCustomerId AND cr.SecondaryCustomerId IS NOT NULL

SQL Serverは、クエリのこの時点(最後から2番目のフィルター演算子の右側のプラン)まで、OrdersCustomersの間で一致する可能性のあるすべてのレコード間でクロス結合を生成しているようです。次に、その条件の各レコードを調べて、実際に一致するかどうかを確認します。フィルタに入る線が本当に太く、出てくる線が本当に細いことに注意してください。これは、その演算子の後、推定行数が21kから4になるためです。私が以前に言ったことを忘れてください、これはおそらく計画の主な問題です。これらの列にインデックスがある場合でも、結合条件が複雑すぎるため、SQLServerはそれらを使用できません。完全結合述語をすぐに使用できないため、必要なレコードだけを探すのではなく、すべてのレコードをマージする計画が発生しています。

私の最初の考えは、CTE custOrdersを2つのデータセットの和集合として言い換えることです。1つはCustomerIdを使用し、もう1つはSecondaryCustomerIdを使用して結合します。これにより、残りのCTEの作業が複製されますが、インデックスを適切に使用できるようになれば、大きなメリットになる可能性があります。

2
Chris Smith

この結合でソートが発生していると思います。

FROM   custOrders c 
       INNER JOIN files f 
               ON c.orderid = f.orderid 

クエリではstatusid列も使用されるため、orderid列とstatusid列を含むファイルにインデックスを作成します。

次の変更も検討することをお勧めします。

  1. 「ac.adminuserid IS NOT NULL」は、adminusersとadmincustomersの間の内部結合でカバーされているため、必要ありません。
  2. 負の条件は処理に費用がかかるため、テスト「f.statusid NOT IN(5、6)」を正の条件(例:In)に変更します。
1
Jeff Siver

私はこの質問がかなり古いことを知っています、しかし私はこれと同じ問題を抱えていて、私のテーブルが突然遅くなった全く異なる理由があることに気づきました。症状は同じで、以前は非常に高速だったビューの更新が遅くなりました。 40%のコストを与える「ソート」。この解決策は誰かに役立つかもしれません、そしてそれは簡単です。テーブルを結合するときは、「likeforlike」ベースで結合していることを確認してください。 IDで2つのテーブルを結合していました。ただし、一方のテーブルではIDがintとして設定され、もう一方のテーブルではnvarcharとして設定されていました。これを修正して、両方を同じタイプとして定義し、ビューを電光石火の速度に戻しました。

うまくいけば、これは、SQLが本当にPEBKACの瞬間であるときに、SQLの何が問題になっているのかを理解するために1週間を費やすのを避けるのに役立つでしょう。

(キーボードと椅子の間に問題があります)

1
DDuffy