ストアドプロシージャとインラインSQL


27

ストアドプロシージャは、実行パスを通じて(アプリケーションのインラインSQLよりも)より効率的であることを知っています。しかし、押されたとき、私はその理由についてあまり知らない。

この技術的な理由を知りたい(後で誰かに説明できるように)。

誰も私が良い答えを策定するのを手伝ってくれますか?


1
適切にパラメータ化クエリは、パフォーマンスの観点から、ちょうどストアドプロシージャとして良いようです。どちらも最初に使用する前にコンパイルされ、両方は後続の実行でキャッシュされた実行プランを再利用し、両方のプランは同じプランキャッシュに格納され、両方は同じ名前で処理されます。現在のSQL Serverでは、ストアドプロシージャのパフォーマンス上の利点はなくなりました。
marc_s

@marc_sは、クエリが同一の場合に当てはまります。ただし、回答で指摘したように、アドホッククエリには、同じように見えるクエリでもパフォーマンスの問題になる可能性のあるいくつかの特性があります。
アーロンバートランド

回答:


42

この感情はある時点では真実だったと思いますが、SQL Serverの現在のバージョンではそうではありません。全体的な問題は、SQL Serverはバッチレベルでしか最適化/コンパイルできないため、昔はアドホックSQLステートメントを適切に最適化できなかったことです。これで、ステートメントレベルの最適化が可能になりました。そのため、アプリケーションから適切にパラメーター化されたクエリは、ストアドプロシージャに埋め込まれたクエリと同じ実行プランを利用できます。

次の理由から、DBA側からのストアドプロシージャの方が好きです(そして、それらのいくつかはパフォーマンスに大きな影響を与える可能性があります)。

  • 同じクエリを再利用する複数のアプリがある場合、ストアドプロシージャは、同じコードを異なるコードベースで同じアドホッククエリを複数回ポイ捨てするのではなく、そのロジックをカプセル化します。同じクエリを再利用するアプリケーションも、逐語的にコピーされない限り、プランキャッシュの膨張の対象になる可能性があります。大文字と小文字の違いでさえ、同じ計画の複数のバージョンが保存される可能性があります(無駄です)。
  • アプリケーションのソースコードにアクセスしたり、高価なトレースを実行したりせずに、クエリが実行していることを検査およびトラブルシューティングして、アプリケーションが実行していることを正確に確認できます。
  • また、アプリケーションが実行できるクエリ、アクセスできるテーブル、コンテキストなどを制御できます(事前に知ることができます)。開発者がアプリケーション内でアドホックなクエリを記述している場合、私が知らない、または予測できないテーブルにアクセスする必要があるたびに、シャツの袖を引っ張ってください。ユーザーがdboにアクセスして、彼らが私を盗聴するのを止めます。通常、これは、開発者がDBAを上回るか、DBAが頑固な場合に行われます。その最後のポイントは私たちの悪い点であり、あなたが必要とするクエリを提供することについてより良くする必要があります。
  • 関連する注意事項として、ストアドプロシージャのセットは、システムで実行されているクエリを正確にインベントリする非常に簡単な方法です。アプリケーションがプロシージャをバイパスして独自のアドホッククエリを送信できるようになると、すぐにそれらを見つけるために、ビジネスサイクル全体をカバーするトレースを実行するか、アプリケーションコード全体を解析する必要があります(再び、クエリのように見えるものを見つけるためにアクセスできない場合があります)。ストアドプロシージャのリストを表示できること(および単一のソースをgrepすること、sys.sql_modules特定のオブジェクトへの参照用の)を表示できると、全員の生活がずっと楽になります。
  • SQLインジェクションを防ぐために、もっと長くすることができます。入力を取得して動的SQLで実行した場合でも、許可されることの多くを制御できます。インラインSQLステートメントを作成するときに開発者が何をしているのか、私には制御できません。
  • アプリケーションのソースコードへのアクセス、変更を行う機能、効果的に行うためのアプリケーション言語の知識、再コンパイルおよび再デプロイする権限(面倒なことは一切ありません)なしで、クエリを最適化できます。アプリなど。これは、アプリが配布されている場合に特に問題になります。
  • ストアドプロシージャ内の特定の設定オプションを強制して、個々のクエリがアプリケーション低速化、SSMSの高速化の対象になることを回避できますか?問題。つまり、アドホッククエリを呼び出す2つの異なるアプリケーションの場合、一方がを持ちSET ANSI_WARNINGS ON、もう一方がを持ちSET ANSI_WARNINGS OFF、それぞれが独自の計画のコピーを持つことになります。取得するプランは、使用するパラメーター、適切な統計などに依存します。それぞれの場合にクエリが最初に呼び出されると、異なるプランになり、パフォーマンスが大きく異なる可能性があります。
  • 特定のORMとは異なり、データ型やパラメーターの使用方法などを制御できます-EFなどの以前のバージョンでは、パラメーターの長さに基づいてクエリをパラメーター化するため、パラメーターN'Smith 'と別のN'があった場合ジョンソン '私は計画の2つの異なるバージョンを手に入れるでしょう。彼らはこれを修正しました。彼らはこれを修正しましたが、他に何が壊れていますか?
  • ORMやその他の「役に立つ」フレームワークとライブラリがまだサポートできないことを行うことができます。

とはいえ、この質問は技術的な議論よりも宗教的な議論を巻き起こす可能性があります。そのような事態が発生した場合、おそらくシャットダウンします。


2
ストアドプロシージャのもう1つの理由は?長く複雑なクエリの場合、クエリをサーバーにプッシュする必要があります。それがsprocでない場合、基本的には「exec sprocname」といくつかのパラメーターをプッシュするだけです。これにより、低速(またはビジー)ネットワークで違いが生じる可能性があります。
デビッドクロウェル14年

0

私は提出者を尊重しますが、「宗教的な理由」ではなく、提供された回答に謙虚に同意しません。言い換えれば、ストアドプロシージャを使用するためのガイダンスの必要性を減らすマイクロソフトが提供している機能はないと考えています。

生のテキストSQLクエリの使用を支持する開発者に提供されるガイダンスには、多くの注意事項を記入する必要があります。そのため、最も慎重なアドバイスは、ストアドプロシージャの使用を大幅に奨励し、開発者チームがプラクティスに参加しないようにすることですSQL SPROC(ストアドプロシージャ)の外部で、コードにSQLステートメントを埋め込む、または生の、プレーンテキストのSQLベースのSQL要求を送信する。

なぜSPROCを使用するのかという質問に対する簡単な答えは、提出者が推測したとおりであると思います。SPROCは解析、最適化、コンパイルされます。そのため、クエリの静的な表現を保存したため、クエリ/実行プランはキャッシュされ、通常はパラメータによってのみ変更されますが、モーフィングの可能性があるコピー/貼り付けられたSQL文の場合はそうではありませんページ間およびコンポーネント/層から、また、多くの場合、データベース名を含めてさまざまなテーブルを呼び出し間で指定できる程度に変更されます。このタイプのダイナミックを許可するアドホックSQL送信により、非常に厳しいルールに従って、DBエンジンがアドホックステートメントのクエリプランを再利用する可能性が大幅に低下します。ここでは、動的アドホッククエリ(提起された質問の精神)と効率的なSystem SPROC sp_executesqlの使用を区別しています。

具体的には、次のコンポーネントがあります。

  • ユーザーコンテキストを保持せず、DBエンジンによる再利用を可能にするシリアルおよび並列クエリプラン。
  • 異なるデータパラメータを持つ新しいユーザーによるクエリプランの再利用を可能にする実行コンテキスト。
  • プロシージャキャッシュ。これは、検索の効率を高めるためにDBエンジンがクエリするものです。

「アドホックステートメント」と呼ばれるWebページからSQLステートメントが発行されると、エンジンは既存の実行プランを探してリクエストを処理します。これはユーザーから送信されたテキストであるため、有効な場合は取り込み、解析、コンパイル、および実行されます。この時点で、クエリコストはゼロになります。クエリコストは、DBエンジンがアルゴリズムを使用して、キャッシュから削除する実行プランを決定するときに使用されます。

アドホッククエリは、デフォルトでゼロの元のクエリコスト値を受け取ります。別のユーザープロセス(または同じプロセス)がまったく同じアドホッククエリテキストを続けて実行すると、現在のクエリコストは元のコンパイルコストにリセットされます。アドホッククエリのコンパイルコストはゼロであるため、これは再利用の可能性の前兆ではありません。明らかに、ゼロは最小値の整数ですが、なぜ排除されるのでしょうか?

メモリのプレッシャーが発生し、頻繁に使用されるサイトがある場合、DBエンジンはクリーンアップアルゴリズムを使用して、プロシージャキャッシュが使用しているメモリを再利用する方法を決定します。現在のクエリコストを使用して、排除する計画を決定します。ご想像のとおり、コストがゼロのプランはキャッシュから削除される最初のプランです。ゼロは本質的に「このプランの現在のユーザーまたは参照がない」ことを意味するためです。

  • 注:アドホック実行プラン-現在のコストは、ユーザープロセスごとに、プランの元のコンパイルコストによって増加します。ただし、プランの最大コストは、元のコンパイルコストを超えることはできません...アドホッククエリの場合...ゼロ。そのため、その値...「ゼロ」によって「増加」します-これは、本質的には最低コスト計画のままであることを意味します。

したがって、このような計画は、メモリのプレッシャーが発生したときに最初に排除される可能性が非常に高くなります。

したがって、「ニーズを超えて」大量のメモリを備えたサーバーを構築している場合、ワークロードを処理するのに「十分な」メモリしかないビジーなサーバーほど頻繁にこの問題が発生することはありません。(申し訳ありませんが、アルゴリズムはそうではありませんが、サーバーのメモリ容量と使用率は多少主観的/相対的です。)

現在、1つまたは複数のポイントについて事実に誤りがある場合、私は間違いなく修正されることにオープンです。

最後に、著者は次のように書いています。

「現在、ステートメントレベルの最適化が行われているため、アプリケーションから適切にパラメーター化されたクエリは、ストアドプロシージャに埋め込まれたクエリと同じ実行プランを利用できます。」

著者は「アドホックワークロード用に最適化」オプションに言及していると思います。

その場合、このオプションを使用すると、完全なクエリプランをプロシージャキャッシュにすぐに送信することを避ける2段階のプロセスが可能になります。そこに小さなクエリスタブを送信するだけです。クエリスタブがまだプロシージャキャッシュにある間に、正確なクエリ呼び出しがサーバーに送り返されると、その時点で完全なクエリ実行プランがプロシージャキャッシュに保存されます。これにより、メモリが節約されます。これにより、メモリが圧迫された場合に、エビクションアルゴリズムがキャッシュされた大きなクエリプランよりも頻繁にスタブを追い出すことができます。繰り返しますが、これはサーバーのメモリと使用率に依存します。

ただし、デフォルトではオフになっているため、このオプションをオンにする必要があります。

最後に、多くの場合、開発者がページ、コンポーネント、その他の場所にSQLを埋め込む理由は、柔軟で、データベースエンジンに動的SQLクエリを送信したいからです。したがって、実際のユースケースでは、アドホッククエリをSQL Serverに送信するときに、同じテキスト(call-over-call)を送信することは、私たちが求めるキャッシュ/効率性と同様に発生することはほとんどありません。

追加情報については、以下を参照してください。

https://technet.microsoft.com/en-us/library/ms181055(v=sql.105).aspx
http://sqlmag.com/database-performance-tuning/don-t-fear-dynamic-sql

ベスト、
ヘンリー


4
私はあなたの投稿のいくつかのパラグラフを注意深く、2、3回読んでいますが、あなたがどんな考えを伝えようとしているのかまだわかりません。場合によっては、文の最後に、文が発言しようとしたことの正反対を言っているように見えることがあります。この提出物を注意深く校正し、編集する必要があります。
ピーターギアケンズ

Pieterのフィードバックに感謝します。この場合、文章を短くして要点を明確にする必要がある可能性があります。元の考えの反対を述べているように見える場所の例を提供していただけますか?とても有難い。
ヘンリー

いいえ、アドホックワークロード向けに最適化するつもりはありません。ステートメントレベルの最適化を意味します。たとえば、SQL Server 2000では、ストアドプロシージャが全体としてコンパイルされるため、アプリが、プロシージャ内の何かに一致したアドホッククエリのプランを再利用する方法はありませんでした。私はピーターに同意すると言います-あなたが言うことの多くは従うのが難しいです。「Microsoftがストアドプロシージャを使用するためのガイダンスの必要性を減らす機能を提供していないと思います。」不必要に複雑で、理解するにはあまりにも多くの解析が必要です。私見では。
アーロンバートランド

1
「アドホック」SQLに対する嫌悪感は、SQLが実行間で何らかの形で変化しているという考えに基づいているようです。これは、パラメータ化が関係している場合はまったく正しくありません。
b_levitt

0

TLDR:インラインSQLがパラメーター化されている限り、この2つの間に目立ったパフォーマンスの違いはありません。

これらが、ストアドプロシージャを徐々に段階的に廃止した理由です。

  • 「ベータ」アプリケーション環境を実行します。これは、本番データベースを共有する本番と並行した環境です。dbコードはアプリケーションレベルにあり、db構造の変更はめったにないので、QAを超えて新しい機能を確認し、運用展開ウィンドウの外で展開を行うことができますが、運用機能と重要ではない修正を提供できます。アプリケーションコードの半分がDBにある場合、これは不可能です。

  • データベースレベル(タコ+ dacpacs)でdevopsを練習します。ただし、ビジネスレイヤー以上は基本的にパージして置換し、逆の回復を行うことができますが、データベースに適用する必要がある増分的で潜在的に破壊的な変更には当てはまりません。したがって、DBの展開をより軽く、頻度を少なくすることをお勧めします。

  • オプションのパラメータに同じコードのほぼ正確なコピーを避けるために、「where @var is null or @ var = table.field」パターンを使用することがよくあります。ストアドプロシージャを使用すると、意図はかなり異なりますが、同じ実行プランを取得する可能性が高いため、パフォーマンスの問題が発生するか、「再コンパイル」ヒントを使用してキャッシュされたプランを削除します。ただし、SQLの最後に「署名」コメントを追加する簡単なコードを使用すると、どの変数がnullであるかに基づいて異なるプランを強制できます(すべての変数の組み合わせに対して異なるプランとして解釈されるのではなく、nullとnullではない)。

  • SQLをその場で少し変更するだけで、結果に劇的な変更を加えることができます。たとえば、「Raw」と「ReportReady」という2つのCTEで終了するステートメントを作成できます。両方のCTEを使用する必要があることを示すものは何もありません

    ...

    select * from {(format)} "

これにより、簡素化されたAPI呼び出しと、複雑なロジックを複製しないようにするために、より詳細に記述する必要があるレポートの両方に、まったく同じビジネスロジックメソッドを使用できます。

  • 「procs only」ルールがある場合、SQLの大部分に大量の冗長性が生じ、最終的にCRUDになります-すべてのパラメーターをバインドし、procシグニチャーにすべてのパラメーターをリストします(そして、別のプロジェクトの別のファイルにいる場合)、これらの単純なパラメーターを列にマッピングします。これにより、かなりばらばらの開発エクスペリエンスが作成されます。

プロシージャを使用する正当な理由があります:

  • セキュリティ-アプリが通過しなければならない別のレイヤーがあります。アプリケーションサービスアカウントがテーブルにアクセスすることを許可されておらず、プロシージャに対する「実行」権限のみを持っている場合、追加の保護があります。コストがかかるため、これは当然のことではありませんが、可能性はあります。

  • 再利用-DBに関連しないビジネスルールをバイパスしないようにするために、ビジネスレイヤーで再利用を主に行うべきだと思いますが、私たちはまだ職業上の低レベルの「どこでも使用」タイプのユーティリティプロシージャと機能を持っています。

procを実際にサポートしていないか、IMOを簡単に軽減できるいくつかの引数があります。

  • 再利用-私はこれを「プラス」として言及しましたが、再利用は主にビジネス層で行われるべきであるとここでも述べたいと思いました。レコードを挿入するprocは、ビジネスレイヤーが他のdb以外のサービスもチェックしている可能性がある場合、「再利用可能」と見なすべきではありません。

  • キャッシュプランの膨張-これが問題になる唯一の方法は、パラメーター化するのではなく値を連結する場合です。プロシージャごとに複数のプランを取得することはめったにないという事実は、クエリに「または」がある場合に実際に痛いことがよくあります

  • ステートメントサイズ-プロシージャ名に対するSQLステートメントの余分なkbは、通常、戻ってくるデータに比べて無視できる程度です。エンティティに問題がなければ、私にも問題はありません。

  • 正確なクエリを確認する-コード内でクエリを見つけやすくするには、呼び出し場所をコメントとしてコードに追加するだけです。コードをC#コードからssmsにコピー可能にすることは、クリエイティブな補間やコメントの使用と同じくらい簡単です。

        //Usage /*{SSMSOnly_}*/Pure Sql To run in SSMS/*{_SSMSOnly}*/
        const string SSMSOnly_ = "*//*<SSMSOnly>/*";
        const string _SSMSOnly = "*/</SSMSOnly>";
        //Usage /*{NetOnly_}{InterpolationVariable}{_NetOnly}*/
        const string NetOnly_ = "*/";
        const string _NetOnly = "/*";
  • SQLインジェクション-クエリをパラメーター化します。できた procが代わりに動的SQLを使用している場合、これは実際に元に戻すことができます。

  • デプロイメントのバイパス-データベースレベルでもdevopsを実践しているため、これはオプションではありません。

  • 「アプリケーションが遅く、SSMSが速い」-これは、両側に影響するプランキャッシュの問題です。設定オプションは、THE ONE SET OFF変数の問題を修正するように見える新しいプランをコンパイルするだけです。これは、異なる結果が表示される理由にのみ答えます。設定オプション自体は、パラメータスニッフィングの問題を修正しません。

  • インラインSQL実行プランはキャッシュされません-単にfalseです。proc名と同じようにパラメーター化されたステートメントがすばやくハッシュされ、そのハッシュによってプランが検索されます。100%同じです。

  • 明確にするために、ORMから生成されたコードではなく、生のインラインSQLについて話しています-せいぜいマイクロORMであるDapperのみを使用しています。

https://weblogs.asp.net/fbouma/38178

https://stackoverflow.com/a/15277/852208

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.