多くの場所でパラメーター化されたアドホックSQLServerクエリを呼び出すC#コードの既存の本体があります。SqlParameter.Sizeを指定することはありません。この場合、SqlParameterクラスがパラメーター値からサイズを推測することが文書化されています。最近、SQL Serverプランのキャッシュ汚染の問題に気づきました。この問題では、パラメーターサイズの個別の組み合わせごとに個別のプランがキャッシュされます。
幸い、SqlParameterを作成するときは常に、単一のユーティリティメソッドを介して作成するため、そのメソッドに数行を追加して、この問題を解決する機会があります。以下を追加することを検討しています。
if((sqlDbType == SqlDbType.VarChar) || (sqlDbType == SqlDbType.NVarChar))
m_sqlParam.Size = -1;
つまり、varcharパラメーターを渡すたびに、varchar(max)として渡します。いくつかの簡単なテストに基づいて、これは正常に機能し、(SQL Profilerおよびsys.dm_exec_cached_plansを介して)アドホッククエリごとにキャッシュに単一のプランがあり、文字列パラメーターのタイプがあることがわかります。現在はvarchar(max)です。
これは非常に簡単な解決策のように思われるため、パフォーマンスを低下させる隠れた欠点がいくつかあるはずです。誰か知っていますか?
(SQL Server 2008以降のみをサポートする必要があることに注意してください。)
更新(1月16日)
はい、パフォーマンスを損なう隠れた欠点があります。
マーティン・スミスに感謝します。マーティン・スミスの答え(以下を参照)は、これを分析する正しい方法を教えてくれました。アプリケーションのUsersテーブルでテストしました。このテーブルには、nvarchar(100)として定義されたEメール列があり、Eメール列に非クラスター化インデックス(IX_Users_Email)があります。Martinのクエリ例を次のように変更しました。
declare @a nvarchar(max) = cast('a' as nvarchar(max))
--declare @a nvarchar(100) = cast('a' as nvarchar(100))
--declare @a nvarchar(4000) = cast('a' as nvarchar(4000))
select Email from Users where Email = @a
コメントを外す「declare」ステートメントのどれに応じて、非常に異なるクエリプランが得られます。nvarchar(100)バージョンとnvarchar(4000)バージョンはどちらも、IX_Users_Emailでインデックスシークを提供します。実際、指定した長さでも同じプランが得られます。一方、nvarchar(max)バージョンでは、IX_Users_Emailのインデックススキャンが行われ、その後に述語を適用するためのFilter演算子が続きます。
私にとってはそれで十分です。シークではなくスキャンを取得する可能性がある場合、この「治癒」は病気よりも悪いです。
新しい提案
SQL Serverがvarcharパラメーターを使用してクエリをパラメーター化するたびに、キャッシュされたプランはパラメーターにvarchar(8000)(またはnvarchar(4000))を使用するだけであることに気付きました。SQL Serverに十分かどうかはわかりますが、私には十分です。私の元の質問(上記)のC#コードを次のように置き換えます:
if(sqlDbType == SqlDbType.VarChar)
m_sqlParam.Size = 8000;
else if(sqlDbType == SqlDbType.NVarChar)
m_sqlParam.Size = 4000;
これにより、サイズ-1を使用した場合と同じようにクエリプランに影響を与えることなく、プランキャッシュの汚染の問題が解決されるようです。しかし、私はこれを使って多くのテストを行ったことがなく、この改訂されたアプローチに関する誰かの意見を聞くことに非常に興味があります。
更新(9月24日)
パラメータ値が最大値よりも長い場合に対処するために、以前のバージョン(上記の新しい提案)を変更する必要がありました。その時点で、varchar(max)にする以外に選択肢はありません。
if((sqlDbType == SqlDbType.VarChar) || (sqlDbType == SqlDbType.NVarChar))
{
m_sqlParam.Size = (sqlDbType == SqlDbType.VarChar) ? 8000 : 4000;
if((value != null) && !(value is DBNull) && (value.ToString().Length > m_sqlParam.Size))
m_sqlParam.Size = -1;
}
このバージョンを約6か月間問題なく使用しています。