如何将 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 不能与准备好的语句一起使用