正确做法是用白名单case表达式实现字段级动态排序、or+is null实现条件过滤,二者均须避免字符串拼接,以防执行计划缓存失效、sql注入及索引无法seek。

直接结论:用 CASE 表达式做字段级动态排序,用 OR + IS NULL 做条件过滤,两者都必须避开字符串拼接——否则执行计划缓存失效、注入风险、索引无法 Seek。
SQL Server 动态排序别拼字符串,改用白名单 CASE
传 @sort_field 参数进 ORDER BY 时,如果写成 'ORDER BY ' + @sort_field,SQL Server 不仅拒绝缓存执行计划,还会让每次查询都重新编译,性能掉得明显。更危险的是,字段名无法参数化,拼接前没用 QUOTENAME() 就可能被注入。
- 字段名必须限定在白名单内,比如只允许
'id'、'name'、'created_at'、'status' - 每个分支返回值类型要一致:数值字段统一转
VARCHAR,日期用CONVERT(VARCHAR(23), col, 126),字符串字段直接取原值 - 必须写
ELSE ''或ELSE 0,否则默认补NULL,导致整列排序位置不可控(SQL Server 默认NULLS FIRST) - 升序降序不能传
'ASC'/'DESC'字符串,而要用@sort_dir BIT控制正负号或双CASE分支
示例片段:
ORDER BY CASE WHEN @sort_dir = 1 THEN name END DESC, CASE WHEN @sort_dir = 0 THEN name END ASC,
WHERE 多条件过滤慎用 ISNULL(),优先选 OR + IS NULL
WHERE status = ISNULL(@status, status) 看着一行搞定,但 SQL Server 无法对表达式列走索引 Seek,只能全表 Scan。尤其当表大、字段区分度低(如只有 3 个状态值)时,性能断崖式下跌。
- 正确写法是:
WHERE (@status IS NULL OR status = @status) AND (@category IS NULL OR category = @category) - 多个
OR条件叠加后,执行计划可能退化为 Scan,此时建议加OPTION (RECOMPILE)让每次执行都重生成计划 - 如果所有参数都为
NULL,条件恒真,务必确认是否允许全表扫描;否则应在存储过程开头加逻辑提前退出
排序字段类型不一致会触发隐式转换,导致排序错乱或报错
常见翻车点:CASE WHEN @sort = 'id' THEN id WHEN @sort = 'name' THEN name END —— id 是 INT,name 是 VARCHAR,SQL Server 会把 id 全转成字符串排,结果 “10”
- 数值类字段统一用
CONVERT(VARCHAR(50), col),别依赖自动转换 - 日期类字段用
CONVERT(VARCHAR(23), col, 126)格式化为 ISO8601 字符串,保证字典序等价于时间序 - 字符串字段可直接使用,但要注意
COLLATE一致性,避免跨排序规则比较出错
复合索引顺序必须和 ORDER BY 严格匹配才能跳过排序
你建了 CREATE INDEX IX_user_status_score ON users(status, score DESC),但查询写成 ORDER BY score DESC, status,SQL Server 就不会复用这个索引,照样走 Sort 算子。
- 索引列顺序、升降序、以及 WHERE 中的等值条件字段,三者必须全部与
ORDER BY一致 - 若排序字段含计算表达式(如
UPPER(name)),索引基本无效,应考虑物化计算列并建持久化索引 - 执行计划里出现高开销的
Sort节点或Warning: No Join Predicate,就是最直接的信号
真正难的不是写出来,而是让优化器信得过你的写法——它不看逻辑是否等价,只认结构是否可预编译、索引是否可 Seek。










