ストアドプロシージャは、実行パスを介して(アプリケーションのインラインSQLよりも)効率的です。しかし、押し付けられたとき、私はその理由についてはあまり理解できません。
これについての技術的な理由を知りたい(後で誰かに説明できるように)。
誰かが私が良い答えを立てるのを手伝ってくれる?
この感情はある時点では真実だったと思いますが、SQL Serverの現在のバージョンではそうではありません。全体の問題は、SQL Serverがバッチレベルでのみ最適化/コンパイルできるため、昔はアドホックSQLステートメントを適切に最適化できなかったことでした。これでステートメントレベルの最適化が行われたため、アプリケーションからの適切にパラメーター化されたクエリは、ストアドプロシージャに埋め込まれたクエリと同じ実行プランを利用できます。
次の理由から、私はDBA側からのストアドプロシージャを好んでいます(それらのいくつかはパフォーマンスに大きな影響を与える可能性があります)。
sys.sql_modules
をgrepする)のリストを表示できると、誰もが簡単に生活できるようになります。SET ANSI_WARNINGS ON
を持つことができ、もう1つはSET ANSI_WARNINGS OFF
を持つことができ、それぞれが独自の計画のコピーを持つことになります。それらが取得する計画は、使用中のパラメーター、配置されている統計などに依存します。どちらの場合も、最初にクエリが呼び出されるときに、計画が異なるため、パフォーマンスが大きく異なる可能性があります。とはいえ、この質問は技術的な議論よりも宗教的な議論を巻き起こす可能性が高いです。この状況が発生した場合は、おそらくシャットダウンします。
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呼び出しと、複雑なロジックを複製しないように詳細に説明する必要があるレポートの両方にまったく同じビジネスロジックメソッドを使用できます。
プロシージャを使用する正当な理由があります:
セキュリティ-ここには、アプリが通過する必要のある別のレイヤーがあります。アプリケーションサービスアカウントがテーブルへのアクセスを許可されておらず、プロシージャに対する "実行"権限しか持っていない場合、追加の保護が提供されます。コストがかかるのでこれは当然のことではありませんが、可能性はあります。
再利用-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のみを使用しています。
私は提出者を尊重しますが、「宗教的な理由」ではなく、提供された回答に謙虚に同意しません。つまり、ストアドプロシージャを使用するためのガイダンスの必要性を減らすためにMicrosoftが提供した機能はないと私は思います。
生のテキストSQLクエリの使用を支持する開発者に提供されるガイダンスは、多くの注意事項で満たす必要があります。そのため、最も賢明なアドバイスは、ストアドプロシージャの使用を大幅に奨励し、開発者チームがプラクティスに従事しないようにすることです。 SQL SPROC(ストアドプロシージャ)の外部で、SQLステートメントをコードに埋め込んだり、未加工の古いテキストベースのSQL要求を送信したりする方法。
なぜSPROCを使用するのかという質問に対する単純な答えは、提出者が推測したとおりだと思います。SPROCは解析、最適化、およびコンパイルされます。そのため、クエリの静的表現を保存し、通常はパラメーターによってのみそれを変更するため、それらのクエリ/実行プランがキャッシュされます。これは、モーフィングされる可能性が高いコピー/貼り付けのSQLステートメントの場合は当てはまりません。ページ間およびコンポーネント/層から、多くの場合、データベース名を含め、さまざまなテーブルを呼び出しごとに指定できる範囲で可変化されます。このタイプの動的アドホックSQL送信を許可すると、DBエンジンがアドホックステートメントのクエリプランを再利用する可能性が大幅に減少します。いくつかの非常に厳しい規則に従って。ここで、私は(提起された質問の趣旨で)動的なアドホッククエリと効率的なシステムSPROC sp_executesqlの使用を区別しています。
具体的には、次のコンポーネントがあります。
「アドホックステートメント」と呼ばれる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
ベスト、
ヘンリー