MySQL存储过程准备语句(动态SQL)参数化

时间:2014-05-08 15:01:41

标签: mysql stored-procedures prepared-statement

我试图编写一个MySQL搜索函数来构建动态sql值并通过预准备语句执行它。显然我想通过参数传递用户输入(搜索词)以确保安全性,但是我不知道如何将一个参数匹配到多个?查询中的标记。可能最能表明我的意思:

CREATE DEFINER=`admin`@`localhost` PROCEDURE `WEBSITE_mainSearch`(
IN searchWordIn VARCHAR(128)
)
BEGIN
DECLARE articlesModule BIT;
SET @query = '';
SET @searchWordIn = searchWordIn;
SELECT articlesModuleEnabled INTO articlesModule FROM sys_options WHERE ID = 1;
SET @query = CONCAT(@query, 'SELECT blockName AS itemName, blockPath AS seoName, blockID AS itemID, MATCH(blockName, blockBody) AGAINST (?) AS relevance, \'block\' AS itemType FROM content_blocks WHERE MATCH(blockName, blockBody) AGAINST (?)') ;
IF articlesModule = 1 THEN
    SET @query = CONCAT(@query, 'UNION SELECT articleName AS itemName, seoName, articleID AS itemID, MATCH(articleName, articleBody) AGAINST (?) AS relevance, \'article\' AS itemType FROM news_articles WHERE MATCH(articleName, articleBody) AGAINST (?)') ;
END IF;
PREPARE stmt FROM @query;


EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn;


DEALLOCATE PREPARE stmt;
END

由于将根据启用的模块动态确定?s的数量,我如何知道在此声明中将searchWordIn作为参数发送多少时间 EXECUTE stmt USING searchWordIn;

谢谢!

1 个答案:

答案 0 :(得分:5)

必须为EXECUTE语句提供一个固定的参数列表,因此您必须准备IF/THEN/ELSE块中执行该语句。

IF articlesModule = 1 THEN
    SET @query = ... UNION ...
    PREPARE stmt FROM @query;
    EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn;
ELSE
    SET @query = ...; /* no UNION */
    PREPARE stmt FROM @query;
    EXECUTE stmt USING @searchWordIn, @searchWordIn;
END IF;

我不知道在MySQL存储过程语言的有限范围内解决这个问题的方法。对我而言,这是在存储过程中不使用动态SQL的另一个好理由。


重新评论:

  

我不能做上面的建议 - 我使用的系统有大约7个模块。

我明白了......您可以使用CASE statement代替IF/THEN/ELSE,但实际上您有2个 7 = 128个不同的查询字符串案例,因为我假设这7个模块中的任何一个都可以被搜索到。

允许您使用查询参数的替代方法是忘记使用UNION,而是以最多运行7个单独的SELECT查询并返回所有查询的方式编写过程作为多个结果集。这就是存储过程的目的。但是您必须在PHP层中编写代码以依次获取每个结果集。也就是说,循环遍历结果集,并在该循环内循环遍历当前结果集的行。请参阅PDO::nextRowset()mysqli::next_result()上的示例。

  

我认为我很安全,只需将搜索词汇入动态SQL

即可

不,如果你这样做就不安全了!使用PHP中的查询参数将字符串传递给CALL WEBSITE_mainSearch(?)对于防止SQL注入是没有用的,如果你那么将该参数值连接到过程内的另一个字符串,并执行动态SQL解析和执行。使用查询参数不会使参数值“安全”,它们只是将这些值与SQL分析阶段分开。

如果在连接字符串时使用MySQL的内置函数QUOTE(),则会更安全。 QUOTE()可以转义特殊字符,就像mysql_real_escape_string()一样。除了它略有不同,因为它也会产生分隔字符串的单引号,就像PDO::quote()那样。

SET @query = CONCAT(@query, 'SELECT blockName AS itemName, blockPath AS seoName, 
  blockID AS itemID, MATCH(blockName, blockBody) AGAINST (',
  QUOTE(searchWordIn), ') AS relevance, \'block\' AS itemType 
  FROM content_blocks WHERE MATCH(blockName, blockBody) AGAINST (',
  QUOTE(searchWordIn),')') ;

更新:还有一个选择:使用UNION添加更多子查询,并保留模块的计数。然后使用CASE根据累计计数使用不同数量的参数执行准备好的查询。

SET @n = 0;
IF articlesModule = 1 THEN
    SET @query = ... UNION ...
    SET @n = @n+1;
END IF;

IF newsModule = 1 THEN
    SET @query = ... UNION ...
    SET @n = @n+1;
END IF;

... and similar for the other 5 modules ...

PREPARE stmt FROM @query;

CASE @n
WHEN 1:
    EXECUTE stmt USING @searchWordIn, @searchWordIn;
WHEN 2:
    EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn;
WHEN 3:
    EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
      @searchWordIn, @searchWordIn;
WHEN 4:
    EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
      @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn;
WHEN 5:
    EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
      @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
      @searchWordIn, @searchWordIn;
WHEN 6:
    EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
      @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
      @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn;
WHEN 7:
    EXECUTE stmt USING @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
      @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn,
      @searchWordIn, @searchWordIn, @searchWordIn, @searchWordIn, 
      @searchWordIn, @searchWordIn;
END;