【问题标题】:MySQL PHP PDO prepared statements - performance issues vs securityMySQL PHP PDO 准备好的语句 - 性能问题与安全性
【发布时间】:2012-06-07 11:32:38
【问题描述】:

我正在考虑使用 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/

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

谢谢

【问题讨论】:

标签: php mysql pdo prepared-statement


【解决方案1】:

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

所有这些。社区公开反对使用mysql_* 函数。

注意:建议的替代方案

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

此功能的替代方案包括:

source

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

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

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

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

【讨论】:

  • 你知道,你应该使用引用块来标记他所说的部分,而不是你所做的部分。
  • @Truth 我认为引用我从来源复制的内容更有意义,因为这是我的大部分答案。
  • 抱歉,您仍然只是在重复您在 php.net 网站上阅读的内容和一般建议。你读过我在我的帖子中附上的文章吗?据说准备好的语句确实需要预留服务器资源。虽然准备好每条语句(甚至在整个程序运行中使用一次)意味着服务器必须为每个用户保留资源!不是这样吗?您建议我购买 Jaguar 进行为期一周的购物真的是真的吗?
  • @MikeB 你对来自 orrd101 at gmail dot com 30-Apr-2012 12:46 at cz.php.net/manual/en/pdo.prepare.php 的帖子有何看法?一个完整的视角。
  • @nemozny:emulate 准备代替。然后您可以减少开销,而无需重新编写代码。您看到性能差异了吗?
【解决方案2】:

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

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

很可能不是。


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

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

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

底线,没有明显的性能问题,但有一个显着的安全优势。因此官方建议使用准备好的语句。

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

【讨论】:

  • 感谢您的贡献,但是当您感觉这是我所问问题的答案时……不。实际上,我正在寻找一些合理的论据。
  • 那么请为您的问题提供一个合理的案例。你测量过性能吗?它会以任何方式影响您网站的运行方式吗?不,因为您甚至还没有构建它。但是您担心可能会浪费几分之一秒...这不是过早的优化吗?有关更多说明,请参阅编辑。
  • 抱歉,这里的编辑规则不好。请告诉我:1.另外编写1行代码有什么好处吗? $dbh->prepare("DELETE FROM x WHERE ?"); $dbh->execute($value); vs $dbh->query("DELETE FROM x WHERE " . $dbh->quote($value)); 值得吗?为什么要费心准备?我说的也是编写代码的性能。
  • @nemozny - 你对 Sylverdrag 和 Mike 的回应让你看起来像是在寻找论据而不是帮助,-1。对人积极!
  • @nemozny 也许您应该自己考虑一下。如果编写函数的人不完全确定它会按预期执行(防止 SQL 注入)并建议您使用另一个函数,他们确信在所有情况下都有效,并且没有性能损失......注意尽管您正在编写“多行代码”,但您输入的字符数是相似的。为什么 *** 这是一个问题?您没有应该编写而不是浪费时间的应用程序吗?
【解决方案3】:

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

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

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

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

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

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

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

【讨论】:

    【解决方案4】:

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

    总结如下:

    • 是的,总是使用准备语句
    • 是的,使用 PDO 而不是 mysqli 而不是 mysql。 如果你切换数据库系统,你需要做的就是更新查询而不是查询、函数调用和参数,因为它支持准备好的语句。
    • 始终清理用户提供的数据,尽管使用带参数的预准备语句
    • 查看 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() 中引发,这有助于开发以确保您的查询是正确的。

    无论是否使用 prepare 对实际性能没有任何好处。

    如果您使用 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% 准确,因此请在合并之前验证更改。它还将为其创建的全局变量增加大量开销。

    【讨论】:

      猜你喜欢
      • 2011-06-02
      • 1970-01-01
      • 2016-09-10
      • 2011-07-13
      • 2011-06-21
      • 2010-11-21
      • 2012-08-12
      • 1970-01-01
      相关资源
      最近更新 更多