[英]ADO.NET: Safe to specify -1 for SqlParameter.Size for all VarChar parameters?
我們有一個現有的C#代碼體,可以在很多地方調用參數化的臨時SQL Server查詢。 我們從不指定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)每個ad-hoc查詢的緩存中現在有一個計划,以及字符串參數的類型現在是varchar(max)。
這似乎是一個簡單的解決方案,必須有一些隱藏的,破壞性能的缺點。 有人知道嗎?
(請注意,我們只需要支持SQL Server 2008及更高版本。)
是的,有一個隱藏的,破壞性能的缺點!
非常感謝Martin Smith,他的回答(見下文)向我指出了正確的分析方法。 我使用我們的應用程序的Users表進行了測試,該表具有定義為nvarchar(100)的Email列,並且在Email列上具有非聚集索引(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
根據我取消評論的“聲明”語句,我得到了一個非常不同的查詢計划。 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的大小那樣對查詢計划產生相同的影響。 但是,我沒有對此進行過大量的測試,我很想聽聽任何人對此修訂方法的意見。
我們必須修改先前版本(上面的New Proposal)來處理參數值超過最大值的情況。 此時,您別無選擇,只能將其設為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;
}
我們已經使用這個版本大約六個月沒有問題。
它並不理想,因為最好指定一個與所涉及的列的數據類型相匹配的參數。
您需要檢查您的查詢計划,看看它們是否仍然合理。
嘗試以下測試
CREATE TABLE #T
(
X VARCHAR(10) PRIMARY KEY
)
DECLARE @A VARCHAR(MAX) = CAST('A' AS VARCHAR(MAX))
SELECT *
FROM #T
WHERE X = @A
給出一個像這樣的計划
SQL Server向計划添加計算標量,該計划調用內部函數GetRangeWithMismatchedTypes
並仍設法執行索引查找( 此處有關隱式轉換的更多詳細信息 )。
一個重要的例子顯示在文章為什么不分區消除工作? 。 該文章中描述的行為也適用於針對varchar(n)
列上分區的表的varchar(max)
參數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.