有人可以清楚地解释为什么mysqli_prepare()/bind_param()比real_escape_string


Can someone clearly explain why mysqli_prepare()/bind_param() is better than real_escape_string()?

好吧,我仍然不太明白。我一直在读,为了正确转义您的MySQL查询,您需要使用mysqli_prepare()mysqli_bind_param()

我尝试使用这种设置,坦率地说,它有点笨拙。当我不需要再次引用变量时,我被困在通过引用传递变量上,而完成相同的任务只是更多的代码行。

我想我只是不明白两者之间的区别是什么:

<?php
$sql = new 'MySQLi(...);
$result = $sql->query('
   UPDATE `table`
   SET
      `field` = "'.$sql->real_escape_string($_REQUEST[$field]).'";
');
?>

<?php
$sql = new 'MySQLi(...);
$stmt = $sql->prepare('
   UPDATE `table`
   SET
      `field` = ?;
');
$value = $_REQUEST[$field];
$stmt->bind_param('s', $value);
$stmt->execute();
$result = $stmt->get_result();
unset($value);
?>

除了更多的代码。

我的意思是,他们是否实现了这一点,以便人们在将值发送到查询之前不会忘记转义值?还是以某种方式更快?

或者当我打算重复使用相同的查询(因为可以重用mysqli_stmt)并在其他情况下使用传统方法时,我应该使用此方法吗?

你正在阅读的内容,你需要使用mysqli_prepare()mysqli_bind_param()函数来"正确转义你的MySQL查询"是错误的。

的确,如果您使用 mysqli_prepare() mysqli_bind_param() ,则不需要(也不应该)"转义"作为绑定参数提供的值。所以,从这个意义上说,你正在阅读的内容有一定的道理。

只有当 SQL 文本(查询的实际文本)中包含不安全的变量时,您才需要"正确转义"变量,通常是通过将变量包装在mysqli_real_escape_string()函数调用中。

(我们注意到,可以使用预准备语句,并且仍然可以在SQL文本中包含未转义的变量,而不是将变量值作为bind_parameters传递。这确实违背了使用预准备语句的目的,但关键是,无论哪种方式,您都可以编写易受攻击的代码。

MySQL现在支持"服务器端"预准备语句(如果在连接中启用了该选项),这是重复执行相同SQL文本的性能优化(在某些情况下)。(这在其他数据库中得到了长期的支持,例如 Oracle,其中使用预准备语句一直是一种熟悉的模式,就像,自永远以来。

问:他们是否实现了 [预准备语句],以便人们在将值发送到查询之前不会忘记转义值?

答:根据不使用预准备语句时易受 SQL 注入攻击的代码示例的数量,尽管有关于mysql_real_escape_string()函数的文档,但您会认为这肯定是充分的理由。

我认为一个很大的好处是,当我们阅读代码时,我们可以将 SQL 语句视为单个字符串文字,而不是一堆变量的串联,带有引号和点以及对mysql_real_escape_string的调用,这对于简单的查询来说还不错,但对于更复杂的查询, 它只是过于繁琐。 使用 ? 占位符使 SQL 语句更易于理解,...没错,我需要查看其他代码行来找出那里填充了什么值。(我认为 Oracle 样式命名参数:fee, :fi, :fo, :fum比位置?, ?, ?, ?表示法更可取。但是拥有静态SQL文本才是真正的好处。

问:还是它以某种方式更快?

正如我之前提到的,使用服务器端预准备语句在性能方面可以具有优势。它并不总是更快,但对于重复执行同一语句,其中唯一的区别是文字值(如重复插入),它可以提供性能提升。

问:或者,当我打算重复使用同一查询(因为可以重复使用mysqli_stmt)并在其他情况下使用传统方法时,我是否应该使用此方法?

这取决于你。我更喜欢使用静态SQL文本。但这确实来自使用Oracle的悠久历史,并且在MySQL中使用相同的模式自然而然地适合。(尽管Perl使用DBI接口,Java使用JDBC和MyBATIS或其他ORM(Hibernate,Glassfish JPA等)。

遵循相同的模式在PHP中感觉很自然;mysqli_和PDO的引入是对晦涩(和滥用)mysql_界面的可喜缓解。

好的代码可以按照任一模式编写。但是我挑战你提前考虑更复杂的SQL语句,以及选择使用mysqli_real_escape_string()并将要执行的动态字符串连接在一起,而不是使用静态SQL文本和绑定参数,是否会使阅读和破译实际正在执行的SQL对于发现自己维护他们没有编写的代码的灵魂来说更加复杂。

我认为研究表明,代码的阅读量是编写代码的十倍,这就是为什么我们努力生成可读、可理解的代码,即使这意味着更多的代码行。(当每个语句都在做一个可识别的事情时,这通常比在一个复杂的语句中阅读一堆串联的函数调用更容易理解。

我认为与其说是后一种方法本身更安全的问题,不如说是鼓励逻辑分离的问题。对于预准备语句,SQL 查询独立于我们使用的值。这意味着,例如,当我们返回并更改查询时,我们不必将一堆不同的值连接到一个字符串中,并且可能会冒着忘记转义输入的风险。使代码更易于维护!

我发现有几个主要的好处写得很好:

  1. 产生编译和优化语句的开销只有一次,尽管语句被多次执行。不所有优化都可以在准备好的语句时执行编译,原因有两个:最佳计划可能取决于参数的特定值,最佳计划可能会更改为表和索引随时间而变化。
  2. 预准备语句对 SQL 注入具有弹性,因为参数值,稍后使用不同的协议,不需要正确转义。如果原始语句模板不是从外部输入派生的,SQL 注入不能发生。

另一方面,如果查询只执行一次,则服务器端准备语句可能会变慢,因为到服务器的额外往返。实现限制也可能导致性能损失:某些版本的MySQL不会缓存准备好的查询的结果,并且某些DBMS(如PostgreSQL)在执行过程中不会执行额外的查询优化。

我想

补充一点,mysqli_bind_param() 已从 PHP 5.4.0 中删除。你应该使用 mysqli_stmt_bind_param()