web-dev-qa-db-ja.com

トップ句を選択すると、長い時間コストが発生する可能性がある理由

次のクエリは、完了するまでに永遠にかかります。しかし、top 1句を削除すると、かなり早く終了します。 big_table_1とbig_table_2は、10 ^ 5レコードの2つのテーブルです。

topclauseは時間コストを削減すると信じていましたが、明らかにここにはありません。なぜ???

select top 10 ServiceRequestID
from 
(
    (select * 
     from  big_table_1
     where big_table_1.StatusId=2
    ) cap1
    inner join
      big_table_2 cap2
    on cap1.ServiceRequestID = cap2.CustomerReferenceNumber
    )
16
smwikipedia

この同じトピックに関する他のstackoverflowの議論があります(下部のリンク)。上記のコメントに記載されているように、インデックスとオプティマイザが混乱して間違ったインデックスを使用していることに関係している可能性があります。

私の最初の考えは、(select * ....)からselect top serviceidを実行していて、オプティマイザーがクエリを内部クエリにプッシュしてインデックスを使用するのが難しい場合があるということです。

次のように書き直すことを検討してください

select top 10 ServiceRequestID  
from  big_table_1
inner join big_table_2 cap2
on cap1.servicerequestid = cap2.customerreferencenumber
and big_table_1.statusid = 2

クエリでは、データベースが結果をマージして返そうとしている可能性があります。その後、外部クエリの上位10に制限します。上記のクエリでは、結果がマージされるときにデータベースが最初の10個の結果を収集するだけで済み、時間の負荷を節約できます。また、servicerequestIDにインデックスが付けられている場合は、必ずそれを使用します。あなたの例では、クエリは、仮想のインデックス付けされていない形式ですでに返されている結果セット内のservicerequestid列を探しています。

それが理にかなっていることを願っています。仮に、オプティマイザーはSQLを入力する形式を採用し、毎回値を返すための最良の方法を見つけることになっていますが、実際には、SQLを組み合わせる方法は、特定の手順が実行される順序に実際に影響を与える可能性があります。 DB。

ORDER BYに関係なく、SELECT TOPは遅い

SQL Serverのインデックス付き列でtop(1)を実行するのが遅いのはなぜですか?

13
user158017

これは、「終了」の意味によっても異なります。 「終了」とは、GUIに表示が表示されることを意味する場合、必ずしもクエリの実行が完了したことを意味するわけではありません。ストリーミングが完了したということではなく、結果がストリーミングされ始めていることを意味している可能性があります。これをサブクエリにラップすると、内部クエリのすべての結果が利用可能になるまで、外部クエリは実際には処理を実行できません。

  • 外側のクエリは、内側のクエリのlast行を「終了」するまでに返すのにかかる時間の長さに依存します
  • 内部クエリを個別に実行するには、結果が表示される前にfirst行が返されるまで待機するだけで済みます。

Oracleには、この種の動作の操作にいくらか関連する「first_rows」と「all_rows」のヒントがありました。 AskTom ディスカッション

内部クエリが最初の行を生成してから最後の行を生成するまでに長い時間がかかる場合、これは何が起こっているかを示している可能性があります。調査の一環として、内部クエリを変更して、結果が返される前にすべての行を強制的に処理するグループ化関数(または順序付け)を持つようにします。これを、外部クエリの時間と比較するために内部クエリが実際にかかる時間の尺度として使用します。


トピックから少し離れて、Oracleで次のようなものをシミュレートしてみるのは興味深いかもしれません。数値をストリーミングするパイプライン関数を作成します。数回(たとえば15)ストリーミングしてから、しばらくスピンしてからさらにストリーミングします。

Jdbcクライアントを使用して、パイプライン化された関数に対してQueryを実行しました。 OracleステートメントのfetchSizeはデフォルトで10です。結果をループしてタイムスタンプ付きで出力します。結果がずれているかどうかを確認します。 Postgresは関数からの結果をストリーミングしないため、Postgresql(RETURN NEXT)でこれをテストできませんでした。

Oracle Pipelined Function

パイプライン化されたテーブル関数は、その行を処理した直後にその行を呼び出し元に返し、行の処理を続行します。クエリが単一の結果行を返す前に、コレクション全体を構築してサーバーに返す必要がないため、応答時間が改善されます。 (また、オブジェクトキャッシュはコレクション全体を実体化する必要がないため、関数に必要なメモリは少なくなります。)

Postgresql RETURN NEXT

注:RETURNNEXTおよびRETURNQUERYの現在の実装では、前述のように、関数から戻る前に結果セット全体が格納されます。つまり、PL/pgSQL関数が非常に大きな結果セットを生成する場合、パフォーマンスが低下する可能性があります。メモリの枯渇を回避するためにデータがディスクに書き込まれますが、結果セット全体が生成されるまで関数自体は返されません。 PL/pgSQLの将来のバージョンでは、ユーザーがこの制限のない集合を返す関数を定義できるようになる可能性があります。

JDBCのデフォルトのフェッチサイズ

statement.setFetchSize(100);

4
Glenn

理由を説明することはできませんが、私はアイデアを与えることができます:

クエリの前にSET ROWCOUNT 10を追加してみてください。それはいくつかのケースで私を助けました。これはスコープ設定であるため、クエリの実行後に元の値に戻す必要があることに注意してください。

説明:SET ROWCOUNT:指定された行数が返された後、SQLServerは照会の処理を停止します。

3
Diego

あなたのようなクエリでも同様の問題が発生しました。順序付けられたクエリですが、トップ句がない場合は1秒かかり、トップ3がある場合の同じクエリには1分かかりました。

トップに変数を使用すると、期待どおりに機能することがわかりました。

あなたのケースのコード:

declare @top int = 10;

select top (@top) ServiceRequestID
from 
(
    (select * 
     from  big_table_1
     where big_table_1.StatusId=2
    ) cap1
    inner join
      big_table_2 cap2
    on cap1.ServiceRequestID = cap2.CustomerReferenceNumber
    )
3

このようなものをデバッグするとき、SQL Serverが2つのクエリをどのように「認識する」かを理解する最も簡単な方法は、クエリプランを調べることです。ヒットCTRL-LクエリビューのSSMSで、結果には、クエリが実際に実行されたときに結果を構築するために使用するロジックが表示されます。

SQL Serverは、テーブルのデータに関する統計を維持します。特定の範囲のデータを含む行数のヒストグラム。これらの統計を収集して使用し、これらのテーブルに対してクエリを実行するための「最良の」方法を予測しようとします。たとえば、一部の入力では特定のサブクエリが100万行を返すと予想され、他の入力では同じサブクエリが1000行を返すことを示唆するデータが含まれている場合があります。これにより、結果を構築するためのさまざまな戦略を選択する可能性があります。たとえば、インデックスシーク(目的のデータに直接ジャンプ)の代わりにテーブルスキャン(テーブルを徹底的に検索)を使用します。統計がデータを適切に表していない場合は、「間違った」戦略を選択して、経験しているものと同様の結果を得ることができます。それがここでの問題かどうかはわかりませんが、それは私が探しているようなものです。

2
David Pope

非常によく似た問題を調査する必要がありました。

SELECT TOP 5 *
FROM t1 JOIN t2 ON t2.t1id = t1.id 
WHERE t1.Code = 'MyCode' 
ORDER BY t2.id DESC

t1には100K行、t2は20M行、t1.Codeの結合テーブルからの平均行数は約35Kです。 t1.Code = 'MyCode'はt2に対応する3行しかない2行にしか一致しないため、実際の結果セットは3行のみです。統計は最新です。

上記のTOP5の場合、クエリには数分かかります。TOP5を削除すると、クエリはすぐに返されます。

TOPがある場合とない場合の計画は完全に異なります。

  • TOPのないプランは、t1.Codeでインデックスシークを使用し、2行を検索し、ネストされたループがt2でインデックスシークを介して3行を結合します。非常に速い。
  • TOPを使用するプランでは、t2でインデックススキャンを使用して2,000万行を生成し、ネストされたループがt1.Codeでインデックスシークを介して2行を結合し、top演算子を適用します。

私のTOPプランを非常に悪くしているのは、t1とt2から選択されている行が最新の行(t1.idとt2.idの最大値)の一部であるということです。クエリオプティマイザーは、均等に分散された平均結果セットから最初の5行を選択する方が、TOP以外のアプローチよりも高速であると想定しています。非常に早い行のt1.codeを使用してこの理論をテストしましたが、同じプランを使用すると応答は1秒未満です。

したがって、少なくとも私の場合、結論は、問題は統計に反映されていない不均一なデータ分布の結果であるということです。

2
Rhys Jones

並べ替えを使用しない限り、TOPは結果を私の知る限りソートしません。

したがって、誰かがすでに示唆しているように、クエリの実行にそれほど時間はかからないと思います。クエリにTOPがない場合は、結果が早く表示されるようになります。

@sql_mommyクエリを使用してみてください。ただし、次のものがあることを確認してください。

クエリをより高速に実行するには、big_table_1にservicerequestidとstatusidのインデックスを作成し、big_table_2にcustomerreferencenumberのインデックスを作成します。非クラスター化インデックスを作成する場合は、非常に高速な結果が得られるインデックスのみのプランを取得する必要があります。

私の記憶が正しければ、TOPの結果はbig_table_1のインデックスと同じ順序になりますが、よくわかりません。

ギースリ

1
Gisli

2つのクエリのパフォーマンスを比較する場合は、これら2つのクエリを同じ状況で(クリーンなメモリバッファを使用して)実行し、ミューメリック統計を取得する必要があります。

クエリごとにこのバッチを実行して、実行時間と統計結果を比較します(本番環境では実行しないでください):

DBCC FREEPROCCACHE
GO

CHECKPOINT 
GO

DBCC DROPCLEANBUFFERS 
GO

SET STATISTICS IO ON
GO

SET STATISTICS TIME ON
GO

-- your query here
GO

SET STATISTICS TIME OFF
GO

SET STATISTICS IO OFF
GO
1
jbl

2つの実行プランを比較することをお勧めします。統計が古くなっている可能性があります。実際の実行プランに違いが見られる場合は、パフォーマンスに違いがあります。

ほとんどの場合、上位10でより良いパフォーマンスが期待されます。あなたの場合、パフォーマンスはより悪くなります。この場合、実行プランの違いだけでなく、推定実行プランと実際の実行プランで返される行数の違いも見られ、SQLエンジンによる決定が不十分になります。 。

統計を再計算した後(そして、統計を実行している間に、インデックスを再構築して)再試行してください

また、where big_table_1.StatusId=2を取り出して、代わりに

select top 10 ServiceRequestID
from  big_table_1 as cap1 INNER JOIN
big_table_2 as cap2
ON cap1.ServiceRequestID = cap2.CustomerReferenceNumber
WHERE cap1.StatusId=2

この形式の方がはるかに読みやすいと思いますが、同じ実行プランに最適化する必要があります(リモートでは最適化されない可能性があります)。返される最終結果は、関係なく同じになります

0
Martijn