必须用sp_executesql参数化,禁用EXEC(@sql):因前者分离代码与数据防注入、复用执行计划提性能;后者每次重编译且用户输入直入SQL,极易导致注入、缓存失效和CPU拉满。

直接在存储过程里拼接 SQL 字符串做多维条件检索,90% 的性能问题和安全漏洞都出在这一步。必须用 sp_executesql + 参数化,否则 EXEC(@sql) 一上生产就可能被注入或缓存失效。
为什么不能用 EXEC(@sql) 拼接条件
手动拼字符串看似简单,但实际踩坑极多:
-
EXEC(@sql)每次执行都触发全新编译,执行计划无法复用,高并发下 CPU 直接拉满 - 用户输入若未严格过滤(比如
@keyword = N''' OR 1=1 --'),立刻变成 SQL 注入入口 - 空值、NULL、空字符串、日期格式不一致等边界情况,拼接逻辑极易漏判,导致查不到数据却无报错
- SQL Server 对
EXEC字符串长度限制为 8000 字节(nvarchar(4000)),超长条件直接截断失败
sp_executesql 动态条件的写法要点
核心是「条件字段只拼骨架,参数全走 @param 绑定」。例如要支持按状态、部门、时间范围、关键词模糊搜索:
DECLARE @sql nvarchar(max) = N' SELECT id, name, status, dept_id, created_at FROM orders WHERE 1=1'; IF @status IS NOT NULL SET @sql += N' AND status = @status'; IF @dept_id > 0 SET @sql += N' AND dept_id = @dept_id'; IF @start_date IS NOT NULL SET @sql += N' AND created_at >= @start_date'; IF @keyword != N'' SET @sql += N' AND name LIKE @keyword'; EXEC sp_executesql @sql, N'@status tinyint, @dept_id int, @start_date datetime2, @keyword nvarchar(100)', @status, @dept_id, @start_date, N'%' + @keyword + N'%';
注意几个硬性细节:
- 所有条件分支必须用
IF显式判断,不能依赖ISNULL(@x, col) = col这类写法——它会强制索引失效 -
@keyword的通配符必须在传参前加好(N'%' + @keyword + N'%'),不能写进 SQL 字符串里 - 参数声明列表(第二个参数)类型必须和变量严格一致,
datetime和datetime2不兼容,int和bigint也不行 - 避免在 WHERE 中对字段函数操作,如
YEAR(created_at) = 2026—— 改用created_at >= ''2026-01-01'' AND created_at
多选枚举值(IN 列表)怎么安全传参
SQL Server 原生不支持数组参数,常见错误是把逗号分隔字符串当 IN 列表用,结果全表扫描。正确做法只有两种:
- 用字符串拆分函数转表(推荐 SQL Server 2016+):
AND status IN (SELECT value FROM STRING_SPLIT(@status_list, '','')),前提是@status_list是nvarchar(4000)以内且不含单引号 - 动态生成参数占位符(更安全但代码略长):
若传入 3 个状态值,构造@s1, @s2, @s3占位符,再用sp_executesql绑定对应变量
切忌用 CHARINDEX('','' + CAST(status AS varchar) + '','', @status_list) > 0 —— 它无法走索引,大数据量时慢到不可接受。
容易被忽略的执行计划陷阱
即使用了 sp_executesql,多维条件仍可能让优化器“猜错”:当某些参数常为空(比如 @keyword 大部分时间是空字符串),SQL Server 可能基于第一次非空值生成的计划去跑后续全是空值的查询,导致严重低效。此时必须加 OPTION (RECOMPILE) 强制重编译,但仅限于真正变化频繁的场景——滥用会增加编译开销。更稳的做法是拆成多个专用存储过程,或用 IF 分支提前路由到不同固定 SQL 片段。











