必须在存储过程开头显式校验参数,因隐式转换无法过滤非法值:如INT参数传入'1 OR 1=1'会被静默转为1,导致逻辑失真;更严重者如SQL注入字符串在转换前已进入流程,引发安全风险。

必须在存储过程逻辑开头就做参数校验,不能依赖客户端或后续SQL语句的隐式转换——否则可能跳过检查、触发错误截断、甚至被绕过注入。
为什么不能靠隐式转换自动“过滤”非法值
SQL Server 或 MySQL 不会因参数类型声明就拒绝非法输入。比如声明 @user_id INT,调用时传入 '1 OR 1=1',引擎会尝试转成 1 并静默成功;表面没报错,但业务逻辑已失真。更危险的是,如果上游传的是 '1; DROP TABLE users; --',而你又在过程里拼字符串,那转换前就已经进来了。
常见错误现象包括:
- 存储过程返回空结果或默认值,但没报错,排查时发现是参数被截断或转义失败
- 日志里查不到异常,但业务数据不一致,最终定位到某次调用传了超长字符串,
NVARCHAR(10)参数被截成前10位 - 用
ISNUMERIC()判断数字——它对'1e4'或'.'也返回 1,根本不可靠
SQL Server 中推荐的参数校验写法
在 BEGIN 后第一段就做显式检查,用 THROW 中断执行并带上下文:
IF @user_id IS NULL OR @user_id 2147483647
THROW 50002, 'Invalid @user_id: must be positive integer within INT range', 1;
IF LEN(@username) = 0 OR LEN(@username) > 50 OR @username NOT LIKE '[a-zA-Z0-9_][a-zA-Z0-9_ ]*'
THROW 50003, 'Invalid @username format or length', 1;
关键点:
- 用
LEN()而非DATALENGTH(),避免多字节字符误判 - 字符串校验必须包含长度上限 + 字符白名单,
NOT LIKE '%[^a-zA-Z0-9_]+'这类写法在含空格或中文场景下会漏判,不如正向限定 - 数字类参数别信
ISNUMERIC(),直接用范围比较;若需支持小数,改用DECIMAL类型声明,并校验精度 - 所有
THROW错误号建议从50001起自定义,避开系统保留号(如 50000)
MySQL 存储过程中参数校验的差异点
MySQL 没有 THROW,得用 SIGNAL,且必须配合 DECLARE ... HANDLER 才能中断流程(否则只警告不终止):
IF @user_id IS NULL OR @user_id <p>注意:</p>
- MySQL 的
SIGNAL必须写在存储过程体内部,不能放在函数或触发器里(除非是 MySQL 5.5+) - 参数类型声明要窄:用
INT UNSIGNED代替INT,用VARCHAR(32)代替TEXT,让越界输入在传入阶段就被截断或报错 - MySQL 不支持
LIKE白名单正则,可用REGEXP '^[a-zA-Z0-9_]{1,50}$',但注意性能开销——高频调用过程里慎用 - 对日期类参数,别只检查
IS_DATE(@dt),要加@dt >= ''2000-01-01'' AND @dt 防止未来无效日期透传
最容易被忽略的是:校验代码本身要放在所有动态 SQL(sp_executesql / PREPARE)之前,且不能包裹在 TRY...CATCH 或 DECLARE CONTINUE HANDLER 里——否则校验失败会被吞掉,过程继续往下跑。











