簡體   English   中英

ADO.NET:安全地為所有VarChar參數指定SqlParameter.Size的-1?

[英]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及更高版本。)

更新(1月16日)

是的,有一個隱藏的,破壞性能的缺點!

非常感謝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的大小那樣對查詢計划產生相同的影響。 但是,我沒有對此進行過大量的測試,我很想聽聽任何人對此修訂方法的意見。

更新(9月24日)

我們必須修改先前版本(上面的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM