[.NET] ADO.NET Parameters.AddWithValue 對於SqlDbType的抉擇

接獲客戶端IT通知,某個交易查詢約需6秒,調查資料存取物件是簡單的ADO.NET Sqlcommand,簡單的語法加上參數化查詢AddWithValue,

疑,AddWithValue

 

後續客戶調整後只要0.05秒,再次碰上幾年前埋頭改legacy code提升效能的噩夢:

AddWithValue 參數化查詢時,在DbType的選擇是採.NET資料類型來決定,若查詢參數型別與實際欄位不同時有時可能會造成額外的成本

 

哈! 這一次我們好好筆記下來以後列入程式碼交付盤查

首先建立一個10萬筆,主鍵是VARCHAR(8)的資料表
CREATE TABLE TableSqlDbType
(C1 VARCHAR(8) NOT NULL,C2 CHAR(5) NOT NULL)

--建立主鍵
ALTER TABLE TableSqlDbType
ADD CONSTRAINT PK_TABLE1 PRIMARY KEY CLUSTERED(C1)

--建立資料
DECLARE @I INT = 0;
WHILE @I < 100000
BEGIN
SET @I+=1;
INSERT INTO TableSqlDbType
	VALUES (@I, @I);
END
先給正確型別(VARCHAR)查詢
DECLARE @ID VARCHAR(8) = '10000';
DECLARE @START DATETIME = GETDATE();
SELECT * FROM TableSqlDbType
 WHERE C1 = @ID
PRINT DATEDIFF(MS,@START,GETDATE());

正確查詢型別(VARCHAR)結果:  費時3毫秒,驚!這麼快..

接下來用不太正確的型別(NVARCHAR)查詢
DECLARE @ID NVARCHAR(8) = '10000';
DECLARE @START DATETIME = GETDATE();
SELECT * FROM TableSqlDbType
 WHERE C1 = @ID
PRINT DATEDIFF(MS,@START,GETDATE());

 

不太正確型別(NVARCHAR)查詢結果:費時420毫秒,驚! 多了百倍。

  • 1.由於NVARCHAR的資料優先順序25較VARCHAR(27)優先,因此在述詞多做了無謂的隱含轉換(implicit conversion)。
  • 2.即使轉換了,搜尋述詞卻用區間方式的比較<>

多了幾個執行計畫步驟,SELECT還出現了警告,但..叢集索引搜尋中CPU成本怎麼看不出來(舉手發問)。

 

好,下一步來看一下AddWithValue對於幾種常用資料型別的預設,先寫一支小程式
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["CN"].ConnectionString))
{
    conn.Open();
    using (SqlCommand cmd = new SqlCommand("", conn))
    {

        cmd.Parameters.AddWithValue("string", string.Format("Aerosmith") );
        cmd.Parameters.AddWithValue("decimal", new decimal(100) );
        cmd.Parameters.AddWithValue("int", 0);
        cmd.Parameters.AddWithValue("Datetime", new DateTime(1900,1,1) );

        Console.WriteLine("C# string  SqlDbType is {0}", cmd.Parameters["string"].SqlDbType);
        Console.WriteLine("C# decimal SqlDbType is {0}", cmd.Parameters["decimal"].SqlDbType);
        Console.WriteLine("C# int     SqlDbType is {0}", cmd.Parameters["int"].SqlDbType);
        Console.WriteLine("C# DatetimeSqlDbType is {0}", cmd.Parameters["Datetime"].SqlDbType);
    }
}

字串預設果然是NVARCHAR,解決的辦法可以將sqlcmd參數化addwithvalue改寫成add: 
cmd.Parameters.Add("string", SqlDbType.VarChar);
cmd.Parameters["string"].Value = "Aerosmith";
如果欄位是NVARCHAR,但查詢條件是VARCHAR?

驚! 雖然型別不同,卻沒有出現警告,仔細觀察搜尋字詞:

搜尋索引鍵[1]: 前置詞: [TSQL2012].[dbo].[TableSqlDbType2].C1 = 純量運算子(CONVERT_IMPLICIT(nvarchar(8),[@ID],0))

由於資料類型優先順序的因素,預設會將較低的輸入變數:VARCHAR(27)隱含轉換為NVARCHAR(25),再來和欄位比較。

 

小結:如果可以,就將sqlparameter盡量指定好型別再和欄位比較,這樣就不用傷腦筋 Database Engine在背後多做轉換。

 

 

參考:

SQLParameterAdd

SQLParameterAddWithValue

SQL Server Data Type Mapping

資料類型優先順序