PDO MySQL:是否使用PDO :: ATTR_EMULATE_PREPARES?

And*_*ley 113 php mysql pdo

这是我到目前为止所读到的PDO::ATTR_EMULATE_PREPARES:

  1. PDO的准备仿真对性能更好,因为MySQL的本机准备绕过了查询缓存.
  2. MySQL的本机准备更好于安全性(防止SQL注入).
  3. MySQL的本机准备更适合错误报告.

我不知道这些陈述是多么真实.选择MySQL接口时我最担心的是阻止SQL注入.第二个问题是表现.

我的应用程序目前使用过程MySQLi(没有预处理语句),并且使用了很多查询缓存.它很少会在单个请求中重复使用预准备语句.我开始转向PDO以获取已准备好的语句的命名参数和安全性.

我正在使用MySQL 5.1.61PHP 5.3.2

我应该PDO::ATTR_EMULATE_PREPARES启用还是不启用?有没有办法既具有查询缓存的性能又具有预准备语句的安全性?

Fra*_*ila 105

回答你的疑虑:

  1. MySQL> = 5.1.17(对于PREPAREEXECUTE语句,> = 5.1.21 )可以在查询缓存中使用准备语句.因此,您的MySQL + PHP版本可以将预准备语句与查询缓存一起使用.但是,请仔细注意在MySQL文档中缓存查询结果的注意事项.有许多类型的查询无法缓存,或者即使它们被缓存也没用.根据我的经验,查询缓存通常不是一个非常大的胜利.查询和模式需要特殊构造才能最大限度地利用缓存.从长远来看,无论如何,应用程序级缓存通常都是必要的.

  2. 原生准备对安全性没有任何影响.伪预处理语句仍然会转义查询参数值,它只会在PDO库中使用字符串而不是使用二进制协议在MySQL服务器上完成.换句话说,无论您的EMULATE_PREPARES设置如何,相同的PDO代码都会同样容易受到攻击(或不易受攻击).唯一的区别是参数替换发生的地方 - EMULATE_PREPARES它发生在PDO库中; 没有EMULATE_PREPARES,它发生在MySQL服务器上.

  3. 没有EMULATE_PREPARES你可能会在准备时而不是在执行时获得语法错误; 与EMULATE_PREPARES您只会在执行时获得语法错误,因为PDO没有查询在执行时间之前提供给MySQL.请注意,这会影响您要编写的代码!特别是如果你正在使用PDO::ERRMODE_EXCEPTION!

另一个考虑因素:

  • prepare()(使用本机预处理语句)有固定成本,因此prepare();execute()使用本机预处理语句可能比使用模拟预准备语句发出纯文本查询要慢一些.在许多数据库系统上,a的查询计划也prepare()被缓存,并且可以与多个连接共享,但我不认为MySQL会这样做.因此,如果不对多个查询重用已准备好的语句对象,则整体执行速度可能会变慢.

作为最终的推荐,我认为对于旧版本的MySQL + PHP,你应该模拟准备好的语句,但是对于你最近的版本,你应该关闭模拟.

在编写了一些使用PDO的应用程序之后,我创建了一个PDO连接功能,它具有我认为最好的设置.你可能应该使用这样的东西或调整到你喜欢的设置:

/**
 * Return PDO handle for a MySQL connection using supplied settings
 *
 * Tries to do the right thing with different php and mysql versions.
 *
 * @param array $settings with keys: host, port, unix_socket, dbname, charset, user, pass. Some may be omitted or NULL.
 * @return PDO
 * @author Francis Avila
 */
function connect_PDO($settings)
{
    $emulate_prepares_below_version = '5.1.17';

    $dsndefaults = array_fill_keys(array('host', 'port', 'unix_socket', 'dbname', 'charset'), null);
    $dsnarr = array_intersect_key($settings, $dsndefaults);
    $dsnarr += $dsndefaults;

    // connection options I like
    $options = array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    );

    // connection charset handling for old php versions
    if ($dsnarr['charset'] and version_compare(PHP_VERSION, '5.3.6', '<')) {
        $options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$dsnarr['charset'];
    }
    $dsnpairs = array();
    foreach ($dsnarr as $k => $v) {
        if ($v===null) continue;
        $dsnpairs[] = "{$k}={$v}";
    }

    $dsn = 'mysql:'.implode(';', $dsnpairs);
    $dbh = new PDO($dsn, $settings['user'], $settings['pass'], $options);

    // Set prepared statement emulation depending on server version
    $serverversion = $dbh->getAttribute(PDO::ATTR_SERVER_VERSION);
    $emulate_prepares = (version_compare($serverversion, $emulate_prepares_below_version, '<'));
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, $emulate_prepares);

    return $dbh;
}
Run Code Online (Sandbox Code Playgroud)

  • Re#2:MySQL接收的值作为参数(对于本机预处理语句)肯定不会被SQL**解析**?所以注入*必须*的风险比使用PDO的准备仿真,其中逃脱(如mysql_real_escape_string`多字节字符有历史遗留问题')任何缺陷仍然会留下一个开放注入攻击低? (24认同)
  • +1很好的答案!但是对于记录,如果使用本机准备,即使在MySQL服务器端,参数也永远不会转义或组合到SQL查询中.当您执行和提供参数时,查询已被解析并转换为MySQL中的内部数据结构.阅读本博客的MySQL优化工程师解释了这个过程:http://guilhembichot.blogspot.com/2014/05/re-factoring-some-internals-of-prepared.html我不是说这意味着本机准备是更好,只要我们相信PDO代码正确地逃避(我这样做). (6认同)
  • 请注意[本回答](http://stackoverflow.com/a/12118602)中的要点,使用引擎盖下的`mysql_real_escape_string`重新准备语句模拟以及可能出现的后续漏洞(在非常特殊的边缘情况下). (5认同)
  • @eggyal,您正在假设准备语句的实现方式.PDO可能在其模拟准备转义中存在错误,但MySQL也可能存在错误.AFAIK,模拟准备没有发现问题,这可能导致参数文字通过非转义. (2认同)
  • 很棒的答案,但我有一个问题:如果关闭EMULATION,执行速度会不会慢?PHP必须将准备好的语句发送给MySQL进行验证,然后才发送参数.因此,如果您使用预准备语句5次,PHP将与MySQL通信6次(而不是5次).这不会让它变慢吗?此外,我认为PDO在验证过程中存在错误的可能性更大,而不是MySQL ...... (2认同)
  • 这种帖子实际上是对“为什么SO是所有开发人员/程序员如此宝贵的学习资源”这一问题的最佳答案。经典的定义看起来应该是多么好的答案。简直太棒了。感谢弗朗西斯。 (2认同)
  • 因此,尽管承认模拟准备可以从根本上改变应用程序行为,但您提供了一个“功能”,可以根据 MySQL 版本任意切换模式。因此,任何愚蠢到使用您的“功能”,然后升级 MySQL 的人都会突然以许多神秘且难以追踪的方式破坏他们的应用程序。杰出的。 (2认同)
  • “本机准备对于安全性没有任何影响。伪准备语句仍然会转义查询参数值,它只是在 PDO 库中使用字符串完成,而不是使用二进制协议在 MySQL 服务器上完成。” 这要么是完全且危险的误传,要么是 MySQL 的实现非常糟糕。这不是参数化查询应该实现的方式;这些参数应该完全绕过解析阶段,防止它们被解释为 SQL 代码的一部分。那么到底是哪一个:危险的错误还是不正确? (2认同)

dal*_*lin 15

我很惊讶没有人提到关闭仿真的最大原因之一。启用仿真后,PDO 将所有整数和浮点数作为字符串返回。当您关闭仿真时,MySQL 中的整数和浮点数将变成 PHP 中的整数和浮点数。

有关更多信息,请参阅此问题的公认答案:PHP + PDO + MySQL:如何在 PHP 中将整数和数字列从 MySQL 返回为整数和数字?.

  • 这在 PHP 版本 &lt; `8.1` 中是正确的。但是,从 8.1 开始,模拟准备将在正确返回整数和浮点数方面与本机准备兼容。有关更多信息,请参阅 [PHP 8.1 升级指南](https://github.com/php/php-src/blob/7b34db0659dda933b1146a0ff249f25acca1d669/UPGRADING#L130)。 (11认同)

Sag*_*ter 10

PDO::ATTR_EMULATE_PREPARES当你的PHP pdo_mysql没有被编译时要小心禁用(打开本机准备)mysqlnd.

因为旧的libmysql与某些功能不完全兼容,所以它可能导致奇怪的错误,例如:

  1. 绑定时丢失64位整数的最高有效位PDO::PARAM_INT(0x12345678AB将在64位机器上裁剪为0x345678AB)
  2. 无法进行简单的查询LOCK TABLES(它抛出SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yet异常)
  3. 需要在下一次查询之前从结果或关闭游标中获取所有行(使用mysqlnd或模拟自动准备它,这对您有用,并且不会与mysql服务器不同步)

当我迁移到libmysql用于pdo_mysql模块的其他服务器时,我在我的简单项目中发现了这些错误.也许还有更多的错误,我不知道.我还测试了新的64位debian jessie,所有列出的错误都出现在我身上apt-get install php5-mysql,并在我消失时消失apt-get install php5-mysqlnd.

PDO::ATTR_EMULATE_PREPARES设置为true(默认值)时 - 无论如何都不会发生这些错误,因为PDO在此模式下根本不使用预准备语句.因此,如果您使用pdo_mysql基于libmysql("mysqlnd"子字符串不会出现pdo_mysql在phpinfo部分的"客户端API版本"字段中) - 您不应该PDO::ATTR_EMULATE_PREPARES关闭.

  • 这种担忧在2019年仍然有效吗?! (3认同)

Wil*_*gan 8

当你运行5.1时我会关闭模拟准备,这意味着PDO将利用本机预准备语句功能.

PDO_MYSQL将利用MySQL 4.1及更高版本中存在的本机预处理语句支持.如果您使用的是旧版本的mysql客户端库,PDO将为您模拟它们.

http://php.net/manual/en/ref.pdo-mysql.php

我为准备好的命名语句和更好的API抛弃了MySQLi for PDO.

然而,为了保持平衡,PDO的执行速度比MySQLi慢得多,但要记住这一点.当我做出选择时,我就知道这一点,并且认为使用更好的API并使用行业标准比使用一个可以忽略不计的更快的库将您与特定引擎联系起来更重要.FWIW我认为PHP团队也对未来的MySQLi也非常看好.


qui*_*tin 5

我建议启用真正的数据库PREPARE调用,因为仿真不会捕获所有内容..例如,它会准备好INSERT;!

var_dump($dbh->prepare('INSERT;'));
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
var_dump($dbh->prepare('INSERT;'));
Run Code Online (Sandbox Code Playgroud)

输出

object(PDOStatement)#2 (1) {
  ["queryString"]=>
  string(7) "INSERT;"
}
bool(false)
Run Code Online (Sandbox Code Playgroud)

我很乐意在实际工作的代码中获得性能提升.

FWIW

PHP版本:PHP 5.4.9-4ubuntu2.4(cli)

MySQL版本:5.5.34-0ubuntu0


Har*_*osh 5

为什么将仿真切换为“假”?

这样做的主要原因是让数据库引擎做准备而不是 PDO 是查询和实际数据分开发送,这增加了安全性。这意味着当参数传递给查询时,试图将 SQL 注入其中的尝试将被阻止,因为 MySQL 准备好的语句仅限于单个查询。这意味着当在参数中传递第二个查询时,真正的准备好的语句将失败。

反对在准备与 PDO 中使用数据库引擎的主要论点是两次访问服务器——一次用于准备,另一次用于传递参数——但我认为增加的安全性是值得的。此外,至少在 MySQL 的情况下,查询缓存自 5.1 版以来就不再是问题。

https://tech.michaelseiler.net/2016/07/04/dont-emulate-prepared-statements-pdo-mysql/

  • [查询缓存消失了](https://dev.mysql.com/doc/refman/5.7/en/query-cache.html) 无论如何:*从 MySQL 5.7.20 开始,查询缓存已被弃用,并在MySQL 8.0.* (3认同)

mag*_*nes 5

作为记录

PDO::ATTR_EMULATE_PREPARES=true

它可能会产生令人讨厌的副作用。它可以将 int 值作为字符串返回。

PHP 7.4,带有 mysqlnd 的 pdo。

使用 PDO::ATTR_EMULATE_PREPARES=true 运行查询

列:id
类型:整数
值:1

使用 PDO::ATTR_EMULATE_PREPARES=false 运行查询

列:id
类型:字符串
值:“1”

在任何情况下,无论配置如何,十进制值始终返回字符串:-(

  • 始终返回十进制值,字符串是唯一正确的方法 (3认同)
  • @YourCommonSense 浮点数不能准确表示 0.2,而小数可以。但是,这是一个 PHP 问题,而不是整个计算机科学问题。许多语言(和数据库)都有可以准确表示数字(例如 0.2)的内在数据类型。PHP 可以,但基本语言中没有固有的数据类型。但说十进制值总是作为字符串返回是唯一正确的方法是meh。假设您希望准确表示 0.2,而不是在“2”之前排序“12”。而且“2”不等于“2.0”。 (2认同)