web-dev-qa-db-ja.com

サブクエリを#tempテーブルと同様に強制的に実行するにはどうすればよいですか?

Mongus Pongが尋ねた質問を繰り返しています 一時テーブルを使用すると、ネストされたクエリよりも高速になるのはなぜですか? これには私に役立つ答えがありません。

私たちのほとんどは、ある時点で、ネストされたクエリが特定の複雑さに達すると、パフォーマンスを維持するために一時テーブルに分割する必要があることに気付きます。これがこれまでで最も実用的な方法であり、これらのプロセスをビューにすることができなくなったことを意味するのは不条理です。また、多くの場合、サードパーティのBIアプリはビューでしかうまく機能しないため、これは非常に重要です。

エンジンが各サブクエリを順番にスプールし、裏返しに機能するようにするには、単純なクエリプラン設定が必要であると確信しています。サブクエリをより選択的にする方法を推測する必要はなく(非常にうまくいくこともあります)、相関するサブクエリの可能性もありません。プログラマーが括弧内の自己完結型コードによって返されることを意図したデータのスタックだけです。

サブクエリから#tableに変更するだけで、120秒から5秒かかることがよくあります。本質的に、オプティマイザーはどこかで大きな間違いを犯しています。確かに、オプティマイザーに正しい順序でテーブルを表示させるには、非常に時間のかかる方法があるかもしれませんが、これでも保証はありません。ここでは、理想的な2秒の実行時間を求めているのではなく、ビューの柔軟性の範囲内で一時テーブルが提供する速度だけを求めています。

私はこれまでここに投稿したことはありませんが、SQLを何年も書いていて、この問題を受け入れたばかりの他の経験豊富な人々のコメントを読んだことがあります。特別なヒントはXです...

25
Adamantish

この動作が見られる理由については、いくつかの考えられる説明があります。いくつかの一般的なものは

  1. サブクエリまたはCTEが繰り返し再評価されている可能性があります。
  2. 部分的な結果を_#temp_テーブルに具体化すると、方程式からいくつかの可能なオプションを削除することにより、プランのその部分に対してより最適な結合順序を強制することができます。
  3. 部分的な結果を_#temp_テーブルに具体化すると、不十分なカーディナリティの見積もりを修正することにより、計画の残りの部分を改善できる場合があります。

最も信頼できる方法は、単に_#temp_テーブルを使用してそれを自分で具体化することです。

ポイント1に関してそれが失敗した場合は、 CTEまたは派生テーブルの中間マテリアライズを強制するためのヒントを提供してください を参照してください。 TOP(large_number) ... ORDER BYを使用すると、結果を繰り返し再評価するのではなく、スプールするように促すことができます。

それが機能したとしても、スプールに関する統計はありません。

ポイント2と3については、希望する計画が得られなかった理由を分析する必要があります。おそらく、sargable述語を使用するようにクエリを書き直したり、統計を更新したりすると、より良い計画が得られる可能性があります。クエリヒントを使用して目的のプランを取得してみてください。

13
Martin Smith

各サブクエリを順番にスプールするようにエンジンに指示するクエリヒントがあるとは思いません。

OPTION (FORCE ORDER)クエリヒントがあり、エンジンに指定された順序でJOINを実行させます。これにより、場合によってはその結果を達成するように誘導される可能性があります。このヒントは時々複雑なクエリに対してより効率的な計画をもたらし、エンジンは次善の計画を主張し続けます。もちろん、オプティマイザーは通常、最良の計画を決定するために信頼されるべきです。

理想的には、CTEまたはサブクエリを「マテリアライズド」または「匿名の一時テーブル」として指定できるクエリヒントがありますが、ありません。

5
Dan Bellandi

別のオプション(この記事の将来の読者向け)は、ユーザー定義関数を使用することです。マルチステートメント関数( ストアドプロシージャ間でデータを共有する方法 で説明されているように)は、SQLServerにサブクエリの結果を実体化させるように見えます。さらに、クエリオプティマイザを支援するために、結果のテーブルで主キーとインデックスを指定できます。この関数は、ビューの一部としてselectステートメントで使用できます。例えば:

CREATE FUNCTION SalesByStore (@storeid varchar(30))
   RETURNS @t TABLE (title varchar(80) NOT NULL PRIMARY KEY,
                     qty   smallint    NOT NULL)  AS
BEGIN
   INSERT @t (title, qty)
      SELECT t.title, s.qty
      FROM   sales s
      JOIN   titles t ON t.title_id = s.title_id
      WHERE  s.stor_id = @storeid
   RETURN
END

CREATE VIEW SalesData As
SELECT * FROM SalesByStore('6380')
3
erdomke

この問題が発生したとき、(私の場合)SQL Serverが条件を誤った順序で評価していることがわかりました。これは、使用できるインデックス(IDX_CreatedOn on TableFoo)。

SELECT bar.*
FROM
    (SELECT * FROM TableFoo WHERE Deleted = 1) foo
    JOIN TableBar bar ON (bar.FooId = foo.Id)
WHERE
foo.CreatedOn > DATEADD(DAY, -7, GETUTCDATE())

サブクエリに別のインデックス(つまり、親クエリなしでサブクエリが実行されたときに使用されるインデックス)を使用するように強制することで、なんとか回避できました。私の場合、PKに切り替えました。これはクエリには意味がありませんが、サブクエリの条件を最初に評価できるようにしました。

SELECT bar.*
FROM
    (SELECT * FROM TableFoo WITH (INDEX([PK_Id]) WHERE Deleted = 1) foo
    JOIN TableBar bar ON (bar.FooId = foo.Id)
WHERE
foo.CreatedOn > DATEADD(DAY, -7, GETUTCDATE())

Deleted列によるフィルタリングは非常に簡単で、その後のCreatedOnによるいくつかの結果のフィルタリングはさらに簡単でした。サブクエリと親クエリの実際の実行プランを比較することで、それを理解することができました。


よりハッキーな解決策(実際には推奨されません)は、TOPを使用して結果を制限することにより、サブクエリを最初に強制的に実行することですが、サブクエリの結果が制限を超えると、将来的に奇妙な問題が発生する可能性があります(あなたはいつでもばかげた何かに制限を設定することができます)。残念ながらTOP 100 PERCENT SQL Serverはそれを無視するため、この目的には使用できません。

0
jahu