web-dev-qa-db-ja.com

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

ストアドプロシージャは、実行パスを介して(アプリケーションのインラインSQLよりも)効率的です。しかし、押し付けられたとき、私はその理由についてはあまり理解できません。

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

誰かが私が良い答えを立てるのを手伝ってくれる?

28
webdad3

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

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

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

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

44
Aaron Bertrand

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

これらが私がゆっくりとストアドプロシージャを段階的に廃止した理由です:

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

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

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

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

    ...

    {(format)}から*を選択

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

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

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

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

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

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

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

  • キャッシュプランの肥大化-これが問題になる唯一の方法は、パラメータ化ではなく値を連結する場合です。クエリに「or」が含まれていると、procごとに複数のプランを取得することはめったにないという事実は、多くの場合、実際にあなたを傷つけます

  • ステートメントサイズ-proc名を超える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インジェクション-クエリをパラメーター化します。できました。プロシージャが動的SQLを代わりに使用している場合、これは実際には元に戻すことができます。

  • デプロイメントのバイパス-データベースレベルでもdevopsを実践するため、これは私たちの選択肢ではありません。

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

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

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

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

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

1
b_levitt

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

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

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

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

  • ユーザーコンテキストを保持せず、DBエンジンによる再利用を可能にするシリアルおよびパラレルクエリプラン。
  • 異なるデータパラメータを持つ新しいユーザーによるクエリプランの再利用を可能にする実行コンテキスト。
  • 私たちが求める効率を生み出すためにDBエンジンがクエリするプロシージャキャッシュ。

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

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

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

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

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

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

さて、私が1つ以上の点について事実上正しくない場合、私は間違いなく修正されることにオープンです。

最後に、著者は書きました:

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

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

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

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

最後に、開発者がSQLをページ、コンポーネント、およびその他の場所に埋め込む主な理由は、柔軟性があり、動的SQLクエリをデータベースエンジンに送信するためです。したがって、実際のユースケースでは、SQL Serverにアドホッククエリを送信するときに、まったく同じテキストであるコールオーバーコールを送信することは、私たちが求めるキャッシュ/効率と同じように起こりそうにありません。

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

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

ベスト、
ヘンリー

0
Henry