真正稳妥的多条件模糊查询方案是用CASE WHEN+CHARINDEX实现模糊条件“可空”,配合参数化WHERE处理精确项,既防注入又保索引可用性;高基数字段建议OPTION(RECOMPILE);多值参数须用STRING_SPLIT或TVP;万级数据模糊搜索应启用全文索引。

直接用动态 SQL 拼接多条件模糊查询,90% 的人会踩注入、性能、执行计划缓存失效这三坑。真正稳妥的做法是:用 CASE WHEN + CHARINDEX 组合规避拼接,再配合参数化 WHERE 条件处理可选精确项——既防注入,又保索引可用性(哪怕只对部分字段)。
用 CASE WHEN + CHARINDEX 实现模糊条件“可空”
这是最常被忽略但最有效的手法。核心逻辑是:当参数为空时,让模糊匹配恒为真;不为空时,才实际做子串查找。它绕开了 LIKE '%@param%' 前导通配符导致索引失效的问题(虽然仍无法走索引,但至少避免了 SQL 注入和语法拼错)。
常见错误现象:
-
WHERE name LIKE '%' + @name + '%'—— 参数为空时变成LIKE '%%',语义上看似全查,但执行计划可能因参数嗅探异常而劣化 - 把多个
OR堆在模糊字段上(如name LIKE @name OR code LIKE @name),导致优化器放弃使用任何索引
实操建议:
- 每个模糊字段单独写一个
CHARINDEX判断,不要合并成单个表达式 - 用
CASE WHEN @name = '' THEN 1 ELSE CHARINDEX(@name, name) END > 0替代LIKE,语义更可控 - 若字段允许 NULL,记得加
IS NOT NULL判断,否则CHARINDEX在 NULL 上返回 NULL,整个条件失效
精确条件(状态、类型等)用 @param IS NULL OR field = @param 写法
这种写法看起来简单,但很多人没意识到它的执行计划风险:SQL Server 可能为 @param = NULL 和 @param = 1 生成同一份执行计划,导致缓存污染或低效扫描。
使用场景:
- 下拉框单选的状态字段(如
status)、整型分类 ID(如type_id) - 日期范围中“起始时间可选”或“截止时间可选”的情形
实操建议:
- 对高基数字段(如主键、唯一索引列),优先考虑用
OPTION (RECOMPILE)强制重编译,避免计划缓存误导 - 日期范围别写成
@start IS NULL OR create_time >= @start,而应拆成两段:先判断是否启用该条件,再参与 WHERE - 避免在同一个 WHERE 子句里混用大量
OR,尤其当字段无索引时,性能下降极快
多值参数(如地区多选)必须拆分后 JOIN 或 IN,不能传逗号字符串直用
前端传来 "1,3,5" 这种字符串,如果直接 WHERE city_id IN (@cities),SQL Server 会把它当做一个字符串字面量,不是三个值——这是线上最常出现的“查不到数据却无报错”的根源。
参数差异:
- 传字符串
"1,3,5"→ 必须用自定义函数(如Get_StrArrayStrOfIndex)或STRING_SPLIT(SQL Server 2016+)拆分 - 传表值参数(TVP)→ 更安全高效,但需应用层配合构造
DataTable或结构化类型
性能影响:
-
STRING_SPLIT返回的是无序结果集,JOIN 前务必显式ORDER BY或加索引提示(如需要) - 拆分函数若未加
SCHEMABINDING,可能被优化器跳过内联,变成嵌套循环低效执行 - 用 TVP 时,注意客户端连接字符串要开启
MultipleActiveResultSets=True,否则批量插入失败
模糊字段太多时,FULLTEXT 是唯一能扛住万级数据的方案
当用户要求“在名称、摘要、标签、备注四个字段同时模糊搜关键词”,且数据量超过 1 万行,CHARINDEX 或 LIKE 就会明显变慢——这不是写法问题,是算法瓶颈。
为什么这样做:
-
CONTAINS和FREETEXT走全文索引,底层是倒排索引结构,响应时间基本不随数据量线性增长 - 支持词干匹配(如搜 “running” 能命中 “run”)、近义词、权重排序,比手工拼 LIKE 精细得多
容易踩的坑:
- 全文索引默认不更新,INSERT/UPDATE 后可能查不到最新数据,需确认填充状态(
sys.fulltext_catalogs) -
CONTAINS(col, '"word"')中的双引号必须转义,写成'"word"',漏掉会语法报错 - 中文分词依赖操作系统语言设置,SQL Server 安装时若未选中文语言包,全文索引对中文支持极弱
复杂点在于:全文检索没法和 CASE WHEN 那套逻辑无缝融合,通常得单独建视图或用 UNION ALL 把全文结果和其他条件结果合并——这意味着你得接受“模糊搜索走一套路径,精确筛选走另一套”,而不是幻想一个 WHERE 子句通吃全部逻辑。











