これは、何年にもわたって激怒している宗教的/政治的議論の1つです。ストアドプロシージャを使用する必要がありますか、それともアプリケーションにアドホッククエリを配置する必要がありますか?いくつかの理由から、私は常にストアドプロシージャの支持者でした:
- クエリがアプリケーションコードで構築されている場合、SQLインジェクション保護を実装できません。開発者はパラメータ化されたクエリを知っているかもしれませんが、それらを適切に使用することを強制するものは何もありません。
- アプリケーションのソースコードに埋め込まれているクエリを調整したり、ベストプラクティスを適用したりすることはできません。
- クエリチューニングの機会を見つけた場合、それをデプロイするには、ストアドプロシージャを変更するだけでなく、アプリケーションコードを再コンパイルして再デプロイする必要があります。
- クエリがアプリケーション内の複数の場所または複数のアプリケーションで使用されており、変更が必要な場合は、複数の場所で変更する必要がありますが、ストアドプロシージャでは、1回だけ変更する必要があります(デプロイメントの問題脇に)。
また、多くの人がORMを支持してストアドプロシージャを捨てていることもわかります。単純なアプリケーションの場合、これはおそらく問題ありませんが、アプリケーションがより複雑になると、選択したORMが特定のクエリパターンを実行できなくなり、ストアドプロシージャを使用するように*強制*される可能性があります。ストアドプロシージャをサポートしている場合、それはです。
これらの議論はすべてかなり説得力があると思いますが、今日話したいことではありません。パフォーマンスについてお話ししたいと思います。
そこにある多くの議論は、単に「ストアドプロシージャの方がパフォーマンスが良い」と言うでしょう。これはある時点でわずかに真実だったかもしれませんが、SQL Serverがオブジェクトレベルではなくステートメントレベルでコンパイルする機能を追加し、optimize for ad hoc workloads
などの強力な機能を取得したためです。 、これはもはや非常に強力な議論ではありません。インデックスの調整と適切なクエリパターンは、ストアドプロシージャの使用を選択するよりも、パフォーマンスにはるかに大きな影響を及ぼします。最近のバージョンでは、他の変数(ローカルでのプロシージャの実行と別の大陸の別のデータセンターでのアプリケーションの実行など)を導入しない限り、まったく同じクエリで顕著なパフォーマンスの違いが見られる場合が多いとは思いません。
とはいえ、アドホッククエリを処理するときに見落とされがちなパフォーマンスの側面があります。それはプランキャッシュです。 optimize for ad hoc workloads
を使用できます 使い捨てプランがキャッシュをいっぱいにするのを防ぐため(SQLskills.comのKimberly Tripp(@KimberlyLTripp)には、これに関するいくつかの優れた情報があります)。これは、クエリがストアドプロシージャ内から実行されるかどうかに関係なく、使い捨てプランに影響します。またはアドホックで実行されます。この設定に関係なく、気付かない可能性のある別の影響は、同一の場合です。 SET
の違いにより、プランはキャッシュ内の複数のスロットを占有します 実際のクエリテキストのオプションまたはマイナーデルタ。 「アプリケーションが遅く、SSMSが速い」という現象全体が、多くの人がSET ARITHABORT
などの設定に関連する問題を解決するのに役立っています。 。今日は、クエリテキストの違いについて話し、それを表示するたびに人々を驚かせる何かを示したいと思いました。
書き込むキャッシュ
AdventureWorks2012を実行している非常に単純なシステムがあるとしましょう。そして、それが役に立たないことを証明するために、optimize for ad hoc workloads
を有効にしました。 :
EXEC sp_configure 'show advanced options', 1; GO RECONFIGURE WITH OVERRIDE; GO EXEC sp_configure 'optimize for ad hoc workloads', 1; GO RECONFIGURE WITH OVERRIDE;
次に、プランキャッシュを解放します:
DBCC FREEPROCCACHE;
ここで、他の点では同一であるクエリのいくつかの単純なバリエーションを生成します。これらのバリエーションは、2人の異なる開発者のコーディングスタイルを表す可能性があります。空白、大文字/小文字などのわずかな違いです。
SELECT TOP (1) SalesOrderID, OrderDate, SubTotal FROM Sales.SalesOrderHeader WHERE SalesOrderID >= 75120 ORDER BY OrderDate DESC; GO -- change >= 75120 to > 75119 (same logic since it's an INT) GO SELECT TOP (1) SalesOrderID, OrderDate, SubTotal FROM Sales.SalesOrderHeader WHERE SalesOrderID > 75119 ORDER BY OrderDate DESC; GO -- change the query to all lower case GO select top (1) salesorderid, orderdate, subtotal from sales.salesorderheader where salesorderid > 75119 order by orderdate desc; GO -- remove the parentheses around the argument for top GO select top 1 salesorderid, orderdate, subtotal from sales.salesorderheader where salesorderid > 75119 order by orderdate desc; GO -- add a space after top 1 GO select top 1 salesorderid, orderdate, subtotal from sales.salesorderheader where salesorderid > 75119 order by orderdate desc; GO -- remove the spaces between the commas GO select top 1 salesorderid,orderdate,subtotal from sales.salesorderheader where salesorderid > 75119 order by orderdate desc; GO
そのバッチを1回実行してから、プランキャッシュを確認すると、基本的にまったく同じ実行プランのコピーが6つあることがわかります。これは、クエリテキストがバイナリハッシュされているためです。つまり、大文字と小文字と空白が違いを生み、それ以外の点では同一のクエリがSQLServerに固有に見える可能性があります。
SELECT [text], size_in_bytes, usecounts, cacheobjtype FROM sys.dm_exec_cached_plans AS p CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t WHERE LOWER(t.[text]) LIKE '%ales.sales'+'orderheader%';
結果:
text | size_in_bytes | 使用回数 | cacheobjtype |
---|---|---|---|
トップ1のsalesorderid、o… | を選択272 | 1 | |
上位1つのsalesorderidを選択、… | 272 | 1 | |
上位1つのsalesorderidを選択、o… | 272 | 1 | |
select top(1)salesorderid、… | 272 | 1 | |
SELECT TOP(1)SalesOrderID、… | 272 | 1 | |
SELECT TOP(1)SalesOrderID、… | 272 | 1 |
したがって、アドホック設定によりSQL Serverは最初の実行時に小さなスタブのみを格納できるため、これはまったく無駄ではありません。ただし、(プロシージャキャッシュを解放せずに)バッチを再度実行すると、もう少し憂慮すべき結果が表示されます。
text | size_in_bytes | 使用回数 | cacheobjtype |
---|---|---|---|
トップ1のsalesorderid、o… | を選択49,152 | 1 | |
上位1つのsalesorderidを選択、… | 49,152 | 1 | |
上位1つのsalesorderidを選択、o… | 49,152 | 1 | |
select top(1)salesorderid、… | 49,152 | 1 | |
SELECT TOP(1)SalesOrderID、… | 49,152 | 1 | |
SELECT TOP(1)SalesOrderID、… | 49,152 | 1 |
パラメータ化が単純であるか強制であるかに関係なく、パラメータ化されたクエリでも同じことが起こります。また、アドホック設定が有効になっていない場合も、より早く発生することを除いて、同じことが起こります。
最終的な結果として、これにより、同じように見えるクエリの場合でも、多くのプランキャッシュの肥大化が発生する可能性があります。1つの開発者がタブでインデントし、もう1つの開発者が4つのスペースでインデントする2つのクエリに至るまでです。チーム全体でこの種の一貫性を強制しようとすることは、退屈なものから不可能なものまで、どこにでもある可能性があることを言う必要はありません。したがって、私の考えでは、これはモジュール化、DRYへの譲歩、およびこのタイプのクエリを単一のストアドプロシージャに集中化することに強い賛成を与えます。
注意事項
もちろん、このクエリをストアドプロシージャに配置すると、そのコピーは1つだけになるため、クエリテキストがわずかに異なる複数のバージョンのクエリが発生する可能性を完全に回避できます。ここで、異なるユーザーが異なる名前で同じストアドプロシージャを作成する可能性があり、各ストアドプロシージャには、クエリテキストのわずかなバリエーションがあると主張することもできます。可能ではありますが、それはまったく別の問題だと思います。 :-)