面對數百上千個 WHERE IN 參數或 LIKE 模糊查詢,直接拼接 SQL 雖簡單但無法重用執行計畫、有注入風險。本文深度對比 4 種 WHERE IN 參數化方案與 2 種 LIKE 參數化寫法,從執行計畫重用、索引利用、參數上限、IO 開銷等維度分析優劣,給出明確選型建議。
-- 直接拼接 (簡單但有風險)
string userIds = "1,2,3,4";
string sql = $"SELECT * FROM Users WHERE UserID IN ({userIds})";
缺點:
❌ 每次參數不同產生新計畫 → 計畫快取膨脹
❌ SQL 注入風險 (若參數來自用戶輸入)
❌ 參數過多超過批次大小限制
-- CHARINDEX 版
SELECT * FROM Users WHERE CHARINDEX(','+LTRIM(STR(UserID))+',', ','+@UserIDs+',') > 0;
-- LIKE 版
SELECT * FROM Users WHERE ','+@UserIDs+',' LIKE '%,'+LTRIM(STR(UserID))+',%';
EXEC('SELECT * FROM Users WHERE UserID IN (' + @UserIDs + ')');
-- 動態建立參數 @UserID1, @UserID2...
SELECT * FROM Users WHERE UserID IN (@UserID1, @UserID2, @UserID3, @UserID4);
-- 暫存表版 (單一參數傳入逗號字串,SP 內部解析填表)
CREATE PROC usp_GetUsers @UserIDs VARCHAR(MAX)
AS
BEGIN
CREATE TABLE #Temp (UserID INT PRIMARY KEY);
DECLARE @Pos INT, @Val VARCHAR(20);
WHILE LEN(@UserIDs) > 0
BEGIN
SET @Pos = CHARINDEX(',', @UserIDs);
IF @Pos = 0 SET @Val = @UserIDs, @UserIDs = '';
ELSE SET @Val = LEFT(@UserIDs, @Pos-1), @UserIDs = RIGHT(@UserIDs, LEN(@UserIDs)-@Pos);
INSERT INTO #Temp VALUES (CAST(@Val AS INT));
END
SELECT u.* FROM Users u INNER JOIN #Temp t ON u.UserID = t.UserID;
DROP TABLE #Temp;
END
| 參數量級 | 查詢頻率 | 推薦方案 | 備註 |
|---|---|---|---|
| < 50 | 高 | 方案 3 (逐參數) | 最低延遲、零額外 IO |
| 50 ~ 2000 | 中/低 | 方案 3 或 4 | 視開發便利度選擇 |
| > 2000 | 任何 | 方案 4 (暫存表/TVP) | 唯一可行方案 |
| 任何 | 極高頻 (千 QPS+) | 方案 3 + 計畫引導/快取 | 避免暫存表建立開銷 |
-- 寫法 1:萬用字元放在參數值
cmd.CommandText = "SELECT * FROM Users WHERE UserName LIKE @Name";
cmd.Parameters.Add("@Name", SqlDbType.VarChar, 200).Value = "rabbit%";
-- 寫法 2:SQL 中串接萬用字元
cmd.CommandText = "SELECT * FROM Users WHERE UserName LIKE @Name + '%'";
cmd.Parameters.Add("@Name", SqlDbType.VarChar, 200).Value = "rabbit";
兩者執行計畫、效能完全相同,選擇團隊慣例即可。注意:前置萬用字元 (%rabbit%) 會導致索引掃描,後置 (rabbit%) 可索引 Seek。
-- 1. 建立使用者定義資料表類型
CREATE TYPE dbo.UserIDTable AS TABLE (UserID INT PRIMARY KEY);
-- 2. SP 參數使用 TVP
CREATE PROC usp_GetUsers_TVP @UserIDs dbo.UserIDTable READONLY
AS
SELECT u.* FROM Users u INNER JOIN @UserIDs t ON u.UserID = t.UserID;
-- 3. C# 呼叫
var dt = new DataTable(); dt.Columns.Add("UserID", typeof(int));
foreach(var id in ids) dt.Rows.Add(id);
cmd.Parameters.AddWithValue("@UserIDs", dt); // 自動推斷 TVP
TVP 優勢:
✅ 強型別、無暫存表建立/刪除開銷
✅ 統計資訊自動維護、計畫穩定
✅ 僅讀、無鎖衝突
→ 大量參數參數化的現代標準解法。
IN (≤2000) 用 逐參數化,簡單高效。IN (>2000) 或追求最佳實踐用 TVP (SQL 2008+) 或 暫存表。CHARINDEX/LIKE 模擬或 EXEC 動態 SQL —— 犧牲索引或計畫重用得不償失。LIKE 參數化任選兩種寫法,關鍵在萬用字元位置決定索引可用性。參數化不只是防注入,更是執行計畫穩定、索引充分利用、系統可擴展的基石。根據參數量級選對方案,讓資料庫跑在最佳狀態。