如何将 MySQLi 准备好的语句与存储过程一起使用

Posted

技术标签:

【中文标题】如何将 MySQLi 准备好的语句与存储过程一起使用【英文标题】:How to use MySQLi Prepared Statements with Stored Procedures 【发布时间】:2011-07-29 01:51:00 【问题描述】:

我正在尝试了解有关 mysql 以及如何防止 SQL 注入的更多信息,因此我的研究将我带到了准备好的语句,这似乎是要走的路。

我也在努力学习如何编写存储过程,现在正在尝试将两者结合起来。不过这方面的信息不多。

目前,在我的 php 测试应用程序中,我有一个函数可以使用普通的 MySQL 命令调用 SP,如下所示:

mysql_query("CALL usp_inserturl('$longurl', '$short_url', '$source')");

我怎样才能对 MySQLi 和 Prepared Statement 执行相同的操作,以使其对注入尽可能安全?

谢谢!

【问题讨论】:

为什么似乎没有人直接回答这个问题(带有代码示例)? 【参考方案1】:

尝试以下方法:

$mysqli= new mysqli(... info ...); 
$query= "call YourSPWithParams(?,?,?)"; 
$stmt = $mysqli->prepare($query); 
$x = 1; $y = 10; $z = 14;
$stmt->bind_param("iii", $x, $y, $z); 
$stmt->execute(); 

【讨论】:

酷!几乎与任何其他 SELECT 声明相似;唯一的区别是您必须使用CALL 启动 SQL。 您能否添加代码来说明如何从$stmt 检索结果? @Gruber 您可以在此处找到示例 #5 中如何检索数据的示例:php.net/manual/en/mysqli.quickstart.stored-procedures.php @Gruber,这是准备好的语句的要点,为进一步的多次重用做准备。您只使参数 bing 一次(它们与您的语句有 ref 关系,然后只需通过例如 loopp 更改参数(变量),并在此循环内多次执行已经准备好的语句。您不必准备 od 绑定参数再次在这个循环中。【参考方案2】:

您可以同时使用它们:只需使用存储过程进行准备:

//prepare and bind SP's parameters with your variables only once
$stmt=$db->prepare("CALL MyStoredProc(?,?)");
$stmt->bind_param('is',$i,$name);

//then change binded variables and execute statement
for($i=1;$i<9;$i++)

  $name="Name".$i;
  $stmt->execute();

请记住,您应该只进行一次准备工作(而不是每次执行都重复一次),然后再执行多次(只需更改之前的参数值)。

【讨论】:

【参考方案3】:

您可能会找到以下使用答案:

MySql: Will using Prepared statements to call a stored procedure be any faster with .NET/Connector?

另外:

仅授予执行权限,以便您的应用程序级用户只能调用存储过程。这样,您的应用程序用户只能通过您的存储过程 API 与数据库交互,他们不能直接:

select, insert, delete, update, truncate, drop, describe, show etc. 

没有比这更安全的了。唯一的例外是,如果您在存储过程中使用了动态 sql,我会不惜一切代价避免 - 或者至少要意识到这样做的危险。

在构建数据库时,例如foo_db,我通常创建两个用户。第一个 foo_dbo(数据库所有者)是拥有数据库并被授予完全权限 (ALL) 的用户,因此他们可以根据需要创建模式对象和操作数据。第二个用户 foo_usr(应用程序用户)仅被授予执行权限,并在我的应用程序代码中用于通过我创建的存储过程 API 访问数据库。

grant all on foo_db.* to foo_dbo@localhost identified by 'pass';

grant execute on foo_db.* to foo_usr@localhost identified by 'pass';

最后,您可以使用 mysql_real_escape_string 改进上面的代码示例:

http://php.net/manual/en/function.mysql-real-escape-string.php

 $sqlCmd = sprintf("call usp_inserturl('%s','%s','%s')", 
  mysql_real_escape_string($longurl),  
  mysql_real_escape_string($shorturl), 
  mysql_real_escape_string($source));

 $result = mysql_query($sqlCmd);

【讨论】:

【参考方案4】:

这个有点棘手,但我最终想出了如何使用使用准备好的语句的存储过程(使用 IN 参数)并通过 PHP 检索数据。本示例使用 PHP 7.4.6 和 MySQL 8.0.21 社区版。

这是存储过程:

CREATE DEFINER=`root`@`loalhost` PROCEDURE `SP_ADMIN_SEARCH_PLEDGORS`(
    IN P_email VARCHAR(60),
    IN P_password_hash VARCHAR(255),
    IN P_filter_field VARCHAR(80),
    IN P_filter_value VARCHAR(255)
)
BEGIN
    #Takes admin credentials (first tow paramaters and searches the pledgors_table where field name (P_filter_field) is LIKE value (%P_filter_value%))
    DECLARE V_admin_id INT(11);
    BEGIN
        GET DIAGNOSTICS CONDITION 1 @ERRNO = MYSQL_ERRNO, @MESSAGE_TEXT = MESSAGE_TEXT;
        SELECT 'ERROR' AS STATUS, CONCAT('MySQL ERROR: ', @ERRNO, ': ', @MESSAGE_TEXT) AS MESSAGE;
    END;
    SELECT admin_id INTO V_admin_id FROM admin_table WHERE password_hash = P_password_hash AND email = P_email;
    IF ISNULL(V_admin_id) = 0 THEN    
        SET @statement = CONCAT('SELECT pledgor_id, email, address, post_code, phone, alt_phone, contact_name
        FROM pledgors_table
        WHERE ',P_filter_field, ' LIKE \'%', P_filter_value, '%\';');
        PREPARE stmnt FROM @statement;
        EXECUTE stmnt;
    ELSE
        SELECT 'ERROR' AS STATUS, 'Bad admin credentials' AS MESSAGE;
    END IF;
END

这是 PHP 脚本

query = 'CALL SP_ADMIN_SEARCH_PLEDGORS(\''.
strtolower($email).'\', \''.
$password_hash.'\', \''.
$filter_field.'\', \''.
$filter_value.'\');';

$errNo = 0;
//$myLink is a mysqli connection
if(mysqli_query($myLink, $query)) 
    do 
        if($result = mysqli_store_result($myLink)) 
            while($row = mysqli_fetch_assoc($result)) 
                $data[] = $row;
            
            mysqli_free_result($result);
        
     while(mysqli_next_result($myLink));

else 
    $errNo = mysqli_errno($myLink);

mysqli_close($myLink);

【讨论】:

以上是关于如何将 MySQLi 准备好的语句与存储过程一起使用的主要内容,如果未能解决你的问题,请参考以下文章

如何在mysqli准备好的语句中使用SQL LIKE子句[重复]

PHP password_verify 不能与准备好的语句一起使用

如何使用mysqli准备好的语句?

如何将准备好的语句与 OleDbDataAdapter 一起使用?

如何使用索引号将数据发送到mysqli(准备好的语句)

如何在 MySQLI 中使用准备好的语句?