应使用PREPARE+EXECUTE配合USING传参,用户输入值必须经TRIM、非空校验及格式验证后转义并单引号包裹,列名/表名须白名单校验+QUOTENAME()包裹,严禁拼接至SQL字符串。

直接拼接 SQL 字符串是最常见也最危险的做法——CONCAT 一用就中招,' OR 1=1 -- 这类注入几乎必然发生。安全可行的路径只有一条:值必须参数化,列名/表名必须白名单校验+转义。
MySQL 中 PREPARE / EXECUTE 怎么安全传参?
MySQL 的 PREPARE 不支持把 WHERE 条件值当参数绑定,只能靠字符串拼接;但拼接时不能直接嵌入用户输入,否则就是裸奔。
- 所有输入值(如
user_name、start_date)必须先做TRIM()+ 非空判断,空字符串或NULL应跳过整个条件 - 日期类参数要提前用
STR_TO_DATE()校验格式,失败则设为NULL,避免拼出非法字符串 - 拼
@sql时,值部分用单引号包裹并手动转义单引号:比如REPLACE(user_name, "'", "''") - 最终执行前检查
@sql是否含非法字符(如分号、UNION SELECT、注释符--或/*),可用正则或简单INSTR()拦截
SQL Server 里 sp_executesql 能不能动态列名?
不能。sp_executesql 允许参数化值,但对象名(列名、表名、排序字段)必须拼接,且必须用 QUOTENAME() 包裹,否则遇到 order、user 这类保留字或带空格的列名直接报错。
- 列名白名单建议存在配置表里,运行时查表比硬编码
IF @col = 'name' OR @col = 'email'更易维护 - 拼接后生成的 SQL 示例:
SELECT ' + QUOTENAME(@col_name) + ' FROM users WHERE status = @status -
@status这类值必须走@params参数定义,不能拼进字符串,否则丢失类型和注入防护 - 别用
EXEC(@sql)替代——它不支持参数化,等于放弃最后一道防线
WHERE 条件怎么避免“全表扫描”陷阱?
用 OR 逻辑兜底(如 (@name IS NULL OR user_name = @name))看似简洁,但多数数据库会因此放弃索引,尤其在复合索引场景下。
- 真正有效的写法是:每个条件独立判断,只拼用户实际提交的有效项,最后组合成干净的
WHERE a = ? AND b LIKE ? - 如果前端传了
department = "",后端应视同未传,不加AND department = '' - 时间范围要拆开处理:
start_time和end_time都非空才加BETWEEN;仅一个有值就只加单边>=或 - MySQL 8.0+ 可考虑用
JSON_CONTAINS()预存合法条件集,避免存储过程里写一堆IF分支
动态条件真正的难点不在语法,而在边界控制:哪个字段允许为空、什么算“有效输入”、保留字怎么映射、索引失效怎么预警——这些都得在拼 SQL 前就定死规则,而不是靠运行时 try-catch 补救。

















