最直接安全的做法是将每个可选条件写成 @param IS NULL OR column = @param 形式,SQL Server、MySQL、PostgreSQL 均支持且优化器在参数非空时仍可能走索引 Seek;避免使用 ISNULL 或 COALESCE 导致不可 SARGable。

WHERE 条件中用 @param IS NULL OR column = @param 判断是否忽略
传入参数为 NULL 时,希望该条件不生效(即“忽略过滤”),最直接安全的做法是把每个可选条件写成 @param IS NULL OR column = @param 形式。SQL Server、MySQL、PostgreSQL 都支持这种写法,且优化器在 @param 有值时仍有机会走索引 Seek。
常见错误是写成 column = ISNULL(@param, column) 或 column = COALESCE(@param, column)——这会让整个表达式不可 SARGable,强制全表 Scan。
- 多个条件叠加后,如果字段区分度低(比如
status只有 3 个值),执行计划可能退化;可加OPTION (RECOMPILE)(SQL Server)或在应用层控制缓存策略 - MySQL 8.0+ 中,
@param IS NULL OR column = @param在某些版本下会抑制索引使用,建议配合FORCE INDEX显式指定 - PostgreSQL 对这类写法优化较好,但若
@param总是 NULL,统计信息可能误导 planner,需定期ANALYZE
MyBatis 里用 <where> + <if> 自动跳过空参数
MyBatis 的 <where> 标签会自动处理开头的 AND,而 <if test="name != null"> 能确保只有非空参数才生成对应条件。它底层仍是预编译参数传递,完全规避 SQL 注入。
注意:不能写成 test="name != ''" 就完事——如果业务允许空字符串作为有效值(如“未填写姓名”),那这个判断反而会误删条件。
-
test表达式支持 OGNL,可组合判断:test="name != null and name.trim().length() > 0" - 模糊查询别用
${name}拼接,改用CONCAT('%', #{name}, '%')或数据库内置函数(如 PostgreSQL 的ILIKE) - 如果参数是整型但可能未传,Java 层建议用
Integer而非int,避免默认值 0 干扰逻辑
排序方向和字段名必须分离处理,不能直接拼 ${sortField} ${sortDir}
升序/降序(ASC/DESC)是 SQL 关键字,不能加引号,所以 #{sortDir} 会失败;但直接用 ${sortDir} 又有注入风险——比如传入 ASC; DROP TABLE users; --。
正确做法是把字段名和方向都白名单校验:
- 字段名:用
<choose>+<when test="sortField == 'create_time'">create_time</when>显式枚举 - 方向:用布尔参数
isAsc,再用<if test="isAsc">ASC</if><if test="!isAsc">DESC</if> - MySQL 8.0+ 支持
ORDER BY FIELD()动态字段,但仅限枚举值,不适用于用户自定义字段
NULL 参数在 WHERE 中漏判会导致全表更新或零行影响
UPDATE 语句里,主键或唯一性条件参数(如 @id)若为 NULL,WHERE id = @id 永远不成立,结果是 0 行被更新;更危险的是写成 WHERE @id IS NULL OR id = @id——一传 NULL 就变成全表更新。
必须前置校验:
- SQL Server:开头加
IF @id IS NULL THROW 50000, 'id required', 1; - MySQL:用
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'id required'; - PostgreSQL:用
RAISE EXCEPTION 'id required' USING ERRCODE = '23502';
动态过滤只适用于非关键条件字段;WHERE 主干必须由确定值驱动,这是最容易被忽略的边界。

















