Eri*_*ver 6 mysql stored-procedures dynamic-sql prepared-statement
我使用的一些存储过程需要根据是否提供了过程输入参数来插入 WHERE 条件。为了避免潜在的注入点,我想对将成为内插标准的一部分的值使用参数绑定。
由于添加到准备好的语句中的条件以及要绑定的参数数量可能因用户输入而异,因此我设计了以下方法来确定将哪些变量传递给 EXECUTE 语句。这有效,但似乎不优雅。
CREATE PROCEDURE foo (IN mandatory INT, IN optional INT, IN optional2 VARCHAR(20))
BEGIN
SELECT
0, '', '', mandatory, optional, optional2
INTO
@params, @sql, @where, @m, @o1, @o2;
IF (@o1 > '' AND @o1 IS NOT NULL) THEN
SET @where = CONCAT(@where, ' AND field = ?');
SET @params = @params + 1;
END IF;
IF (@o2 > '' AND @o2 IS NOT NULL) THEN
SET @where = CONCAT(@where, ' AND field2 = ?');
SET @params = @params + 3;
END IF;
SET @sql = CONCAT('
SELECT id, bar FROM table
WHERE
baz = ?
', @where
);
PREPARE STMT FROM @sql;
CASE @params
WHEN 0 THEN EXECUTE STMT USING @m;
WHEN 1 THEN EXECUTE STMT USING @m, @o1;
WHEN 3 THEN EXECUTE STMT USING @m, @o2;
WHEN 4 THEN EXECUTE STMT USING @m, @o1, @o2;
END CASE;
DEALLOCATE PREPARE STMT;
END$$
Run Code Online (Sandbox Code Playgroud)
我知道替代方案:
但是,我想知道是否还有其他人遇到过这种纯粹使用 SQL 处理 EXECUTE 语句的动态构造的愿望。
至少在这个例子中,有一种比这更简单的方法。请记住,优化器总是试图以这样一种方式来计划查询执行,即涉及检索有效结果集所需工作量最少的路径将是所选择的路径。
SELECT * FROM t1 WHERE (col1 = 'foo') OR (1 = 1);
Run Code Online (Sandbox Code Playgroud)
服务器将始终返回所有行,因为 1 = 1 包含得出结论“是的,该行与WHERE
子句匹配”所需的所有真实性......并且它不会打扰扫描 col1 中的值以查看它们是否包含 'foo ' 因为优化器已经找到了一种更明显、更简单、“成本更低”的方法......(any expression) OR (TRUE)
总是正确的......而通往真相的最短路径是优化器应该找到并采用的路径。它不需要1 = 1
逐行评估,因为这是一个常量表达式。
您甚至不需要准备好的语句,只需在过程中进行查询:
SELECT id, bar
FROM table
WHERE baz = mandatory
AND (optional IS NULL OR field = optional)
AND (optional2 IS NULL OR field2 = optional2);
Run Code Online (Sandbox Code Playgroud)
如果程序变量“optional”为空,优化器意识到整个表达式(在该行的括号中)对于每一行都为真,无论如何,因为在运行查询的上下文中,“optional”是一个常量价值,并且optional is NULL
永远是真的,就像1 = 1
永远是真的一样。优化器意识到这也是一个常量表达式。“字段”的内容永远不需要评估,因为OR
表达式已预先确定为真。可选 2 也是如此。
另一方面,如果“可选”不为空,则被optional IS NULL
优化掉,因为该表达式不可能为真,而你只剩下AND (field = optional)
. 同样,对于“optional2”。
在每种情况下,优化器都会留下仍然可以适当使用索引的查询。
由于查询在过程体中,而不是准备好的语句,SQL 注入是不可能的,因为服务器没有机会模糊变量中的数据和文字查询之间的区别。