7

我正在考虑使用 InnoDB(现在的 mysql_query 和 MyISAM)将一些开源应用程序重写为 PDO 和事务。

我的问题是:哪些情况下使用准备好的语句是合理的?

因为在我阅读的所有地方(甚至在此处的许多帖子中)都指出,由于 1. 安全性和 2. 性能,我应该随时随地使用准备好的语句。甚至 PHP 手册也建议使用准备好的语句,而不是提及转义的东西。

你不能否认安全机制。但是一遍又一遍地思考它会想到每次都必须准备语句然后使用它一次......这没有任何意义。虽然必须在单个语句中插入 1000 次一些变量,但这是有道理的,但很明显。但这不是普通 eshop 或 board 的基础。

那么如何克服呢?我可以在整个应用程序范围内准备我的陈述并具体命名吗?我可以准备几个不同的语句并按名称使用它们吗?因为这是我想到的唯一合理的解决方案(除了 1000x 的东西)。

我发现有一个名为 $pdo->quote 的 mysql_real_escape 也用于单个查询。为什么不使用这个?为什么要费心准备?

您如何看待这篇出色的文章? http://blog.ulf-wendel.de/2008/pdo_mysqlnd-prepared-statements-again/

您同意准备报表所造成的开销吗?

谢谢

4

4 回答 4

9

我认为这属于“过早优化”类别。

开销有多大?你量过吗?它会影响您的服务器性能吗?

很可能不是。


从好的方面来说,您在安全性方面获得了不可否认的收益(这应该是任何基于互联网的商店的主要关注点)。

不利的一面是,您有可能影响性能的风险。在您提供的链接中,它表明在某些情况下,执行不良的 PDO 准备会导致性能略低于未准备好的语句。5000 次运行的性能差异为 0.298 秒。

微不足道。当您意识到“未准备好”的查询在没有输入清理例程的情况下运行时更是如此,而这些例程是使它们在实时环境中安全所需的。如果你不使用准备好的查询,你需要某种形式的输入清理来防止 SQL 攻击,并且根据它是如何完成的,你可能需要对结果集进行按摩。

最重要的是,没有显着的性能问题,但有显着的安全优势。因此官方建议使用准备好的语句。

在您的问题中,您谈到了“普通电子商店”。“普通eshop”永远不会有足够的流量来担心性能问题,如果有的话。另一端的安全问题...

于 2012-06-02T15:48:34.130 回答
7

我的问题是:哪些情况下使用准备好的语句是合理的?

他们全部。社区公开反对mysql_*功能的使用。

注意:建议的替代品

不鼓励使用此扩展程序。相反,应该使用 MySQLi 或 PDO_MySQL 扩展。另请参阅 MySQL:选择 API 以获取更多信息。

此功能的替代方案包括:

资源

但是一遍又一遍地思考它会想到每次都必须准备语句然后使用它一次..这没有意义

您正在用 Geo 换 Jaguar,并抱怨您不喜欢 Jaguar,因为您并不总是使用座椅加热器。您不必始终如一地使用库的每个功能就意味着它很好。

我发现有一个名为 $pdo->quote 的 mysql_real_escape 也用于单个查询。为什么不使用这个?为什么要费心准备?

如果您使用此函数构建 SQL 语句,强烈建议您使用 PDO::prepare() 来准备带有绑定参数的 SQL 语句,而不是使用 PDO::quote() 将用户输入插入到 SQL 语句中。带有绑定参数的预处理语句不仅更便携、更方便、不受 SQL 注入的影响,而且执行起来通常比插值查询快得多,因为服务器端和客户端都可以缓存查询的编译形式。资源

于 2012-06-02T15:35:23.317 回答
2

我的问题是:哪些情况下使用准备好的语句是合理的?

实际上,这很难说。特别是因为您甚至没有说出您在这里谈论的是哪个开源应用程序。

举个例子:对于一个超级蹩脚的留言簿应用程序,带有准备好的语句的 PDO 将是完美的选择,对于 99% 的所有其他开源应用程序也是如此。但对于某些人来说,这实际上可以有所作为。这里重要的部分是:你没有告诉任何关于应用程序的事情。

由于数据库对应用程序而言并非不重要,反之亦然:应用程序对数据库而言并非不重要。

因此,您要么需要分享更多有关您询问的“神秘”开源应用程序的信息,要么需要告诉我们您究竟想知道什么。因为一般来说,很简单:拿 PDO。但具体来说,还是有区别的,所以你需要告诉我们具体的应用是什么,否则你的问题已经回答了。

顺便说一句,如果应用程序是 mysql_* 风格的,用 mysqli_* 接口替换会容易得多。如果你做了一些真正的重写,即使只是为了好玩,你也会看到的。

所以最好在这里添加更多的肉,或者接受一些不太精确的答案。

于 2012-06-03T23:54:12.997 回答
0

虽然这个问题相当古老,但有些主题并没有真正讨论,应该在这里概述,以供其他研究与 OP 相同的人使用。

总结以下所有内容:

  • 是的,总是使用准备语句
  • 是的,通过 mysqli 在 mysql 上使用 PDO。这样,如果您切换数据库系统,您只需更新查询而不是查询、函数调用和参数,因为它支持准备好的语句。
  • 尽管使用带参数的准备好的语句,但始终清理用户提供的数据
  • 研究 DBAL(数据库抽象层)以轻松处理所有这些因素并操纵查询以满足您的需求。

有一个主题 PDO::ATTR_EMULATE_PREPARES 将提高在 MySQL >= 5.1.21 中调用缓存查询的性能,当仿真关闭时,默认情况下是启用的。这意味着 PHP 将在执行之前模拟准备将其发送到实际数据库。模拟和非模拟之间的时间通常可以忽略不计,除非使用外部数据库(不是本地主机),例如在云上,它可能具有异常高的 ping 率。

缓存也取决于 my.cnf 中的 MySQL 设置,但 MySQL 优化超出了本文的范围。

<?php
$pdo = new \PDO($connection_string); 
$pdo->setAttribute( \PDO::ATTR_EMULATE_PREPARES, false );
?>

所以请记住这一点,因为 mysqli_ 不提供用于客户端仿真的 API,并且总是使用 MySQL 来准备语句。 http://www.php.net/manual/en/mysqli.quickstart.prepared-statements.php

尽管具有相似的功能,但还是存在差异,您可能需要一个 API 提供而另一个 API 不提供的功能。请参阅 PHP 关于选择一种 API 的参考:http ://www.php.net/manual/en/mysqlinfo.api.choosing.php

因此,这几乎与您在应用程序范围内定义语句的要求一致,因为可缓存查询将被缓存在 MySQL 服务器上,并且不需要在应用程序范围内进行准备。另一个好处是您的查询中的异常将在prepare() 而不是execute() 中抛出,这有助于开发以确保您的查询是正确的。

不管是否使用准备都没有现实世界的性能优势。

如果您使用 InnoDB for MySQL,准备好的语句的另一个好处是使用事务。您可以启动一个事务、插入一条记录、获取最后一个插入 id、更新另一个表、从另一个表中删除,如果在此过程中出现任何故障,您可以 rollBack() 到事务发生之前。否则,如果您选择提交更改。例如处理一个新订单并将用户的最后一个订单列设置为新订单 ID,并删除一个挂单,但提供的付款类型不符合从 order_flags 表下订单的条件,因此您可以 rollBack()并向用户显示友好的错误消息。

至于安全性,我很困惑没有人提到这一点。将任何用户提供的数据发送到包括 PHP 和 MySQL 在内的任何系统时,对其进行清理和标准化。是的,准备好的语句在转义数据时确实提供了一些安全性,但它不是 100% 的防弹。

因此,始终使用准备好的语句比没有真正的性能损失要好得多,并且缓存有一些好处,但您仍然应该清理用户提供的数据。第一步是将变量类型转换为您正在使用的所需数据类型。使用对象将进一步简化这一点,因为您在数据类型的单个模型中工作,而不是每次使用相同数据时都必须记住它。

要添加到上述内容,您应该查看使用 PDO 的数据库抽象层。例如 Doctrine DBAL:http ://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html

使用 DBAL+PDO 的额外好处是

  • 您可以标准化并缩短您必须完成的工作量。
  • 帮助清理用户提供的数据
  • 轻松操作复杂的查询
  • 使用嵌套事务
  • 在数据库之间轻松切换
  • 您的代码在其他项目中变得更加可移植和可用

例如,我扩展了 PDO 并覆盖了 query()、fetchAll() 和 fetch() 方法,以便它们始终使用准备好的语句,这样我就可以在 fetch() 或 fetchAll() 中编写 SQL 语句,而不必编写一切都出来了。例如:

<?php
$pdo = new PDOEnhanced( $connection );
$pdo->fetchAll( "SELECT * FROM foo WHERE bar = 'hi'", PDO::FETCH_OBJ ); 

//would automatically provide
$stmt = $pdo->prepare( "SELECT * FROM foo WHERE bar=?" );
$stmt->execute( array( 'hi' ) );
$resultSet = $stmt->fetchAll( PDO::FETCH_OBJ )
?>

至于建议 mysql_* 风格的人,用 mysqli_* API 替换要容易得多。事实并非如此。大部分 mysql_* 函数被遗漏或与 mysqli_* 有参数更改参见:http ://php.net/manual/en/mysqli.summary.php

但是,您可以获得 Oracle 发布的转换器以简化流程:https ://wikis.oracle.com/display/mysql/Converting+to+MySQLi

请记住,它是一个文件源文本解析器,并非 100% 准确,因此请在合并之前验证更改。它还将为其创建的全局变量增加大量开销。

于 2014-01-17T20:08:41.850 回答