典型場景:前端勾選多行 → 傳遞 ID 列表 → 後台 `WHERE Id IN (...)`。若拼接字符串構造 SQL,面臨注入、計劃緩存污染、長度限制 (8000/MAX) 三重風險。
| 方案 | 適用版本 | 性能 | 類型安全 | 複雜度 | 推薦指數 |
|---|---|---|---|---|---|
| 字符串分割 (Udf/Split) | 2000+ | 低 (標量函數/CLR/JSON 解析開銷) | 無 (全字符串) | 低 | ⭐⭐ (存量系統維護) |
| XML 參數 | 2005+ | 中 (XML 解析 CPU 密集) | 強 (XSD 可驗證) | 中 | ⭐⭐⭐ (異構/舊版) |
| JSON 參數 | 2016+ | 中高 (原生 JSON 函數) | 半強 (Schema 可選) | 低 | ⭐⭐⭐⭐ (現代應用首選) |
| 表值參數 (TVP) | 2008+ | 最高 (內存表變量、統計信息、計劃復用) | 最強 (嚴格 Schema) | 中 (需創建類型) | ⭐⭐⭐⭐⭐ 企業級標準 |
CREATE FUNCTION dbo.SplitString (@List NVARCHAR(MAX), @Delim NCHAR(1))
RETURNS @T TABLE (Item NVARCHAR(4000))
AS
BEGIN
DECLARE @Pos INT = 1, @Next INT;
WHILE @Pos <= LEN(@List)
BEGIN
SET @Next = CHARINDEX(@Delim, @List, @Pos);
IF @Next = 0 SET @Next = LEN(@List) + 1;
INSERT @T VALUES (SUBSTRING(@List, @Pos, @Next - @Pos));
SET @Pos = @Next + 1;
END
RETURN;
END;
GO
-- 調用
SELECT * FROM dbo.SplitString(N'1,2,3,4,5', N',');
CREATE PROC dbo.usp_DeleteUsers @IdList NVARCHAR(MAX)
AS
DELETE u FROM dbo.Users u
JOIN dbo.SplitString(@IdList, N',') s ON u.Id = s.Item;
GO
-- 客戶端構造 XML
DECLARE @Xml XML = N'1 2 3 ';
-- 存儲過程解析
CREATE PROC dbo.usp_ProcessXml @Data XML
AS
DELETE u FROM dbo.Users u
JOIN @Data.nodes('/Ids/Id') x(i) ON u.Id = x.i.value('.', 'INT');
GO
優勢:支持巢狀結構、屬性、XSD 驗證;劣勢:XML 解析 CPU 高、序列化體積大、客戶端構造繁瑣。
-- 客戶端傳遞 JSON 字符串
DECLARE @Json NVARCHAR(MAX) = N'[1,2,3,4,5]';
-- 存儲過程解析 (OPENJSON 需兼容性級別 130+)
CREATE PROC dbo.usp_ProcessJson @Json NVARCHAR(MAX)
AS
DELETE u FROM dbo.Users u
JOIN OPENJSON(@Json) j ON u.Id = j.value;
GO
優勢:原生函數、輕量、JavaScript 直傳、無需預定義類型;劣勢:2016 以下不可用、大數組解析較 XML 慢、無強 Schema。
CREATE TYPE dbo.IdList AS TABLE (Id INT PRIMARY KEY); -- 可擴展多列
GO
CREATE PROC dbo.usp_BatchDeleteUsers @Ids dbo.IdList READONLY
AS
DELETE u FROM dbo.Users u
JOIN @Ids i ON u.Id = i.Id;
GO
// C# 示例:DataTable 方式
var dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
foreach (var id in selectedIds) dt.Rows.Add(id);
using (var cmd = new SqlCommand("usp_BatchDeleteUsers", conn) { CommandType = CommandType.StoredProcedure })
{
var p = cmd.Parameters.AddWithValue("@Ids", dt);
p.SqlDbType = SqlDbType.Structured;
p.TypeName = "dbo.IdList";
cmd.ExecuteNonQuery();
}
| 坑點 | 後果 | 規避 |
|---|---|---|
| TVP 忘加 READONLY | 語法錯誤 | 模板固化 `CREATE PROC ... @Tvp Type READONLY` |
| 字符串分割函數用標量 UDF (RBAR) | 性能災難 | 改用內聯 TVF / Numbers 表 / JSON/OPENJSON / TVP |
| JSON 大數組 (>10k) 解析超時 | CPU 飆升 | 分批調用、或改 TVP |
| XML 參數未驗證 XSD | 惡意結構導致解析錯誤/拒絕服務 | 存儲過程首行 `DECLARE @x XML = @Data; -- 隱式驗證` 或顯式 XSD |