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


117

这是我到目前为止阅读的内容PDO::ATTR_EMULATE_PREPARES

  1. 由于MySQL的本机准备绕过了查询缓存,因此PDO的准备仿真在性能上更好
  2. MySQL的本机准备更好地提高了安全性(防止SQL注入)
  3. MySQL的本机准备更好地用于错误报告

我不知道这些陈述的真实性。在选择MySQL接口时,我最大的担心是防止SQL注入。第二个问题是性能。

我的应用程序当前使用过程MySQLi(没有准备好的语句),并且充分利用了查询缓存。它很少会在单个请求中重复使用准备好的语句。我开始使用PDO来获取命名参数和已准备好的语句的安全性。

我正在使用MySQL 5.1.61PHP 5.3.2

我应该保持PDO::ATTR_EMULATE_PREPARES启用状态吗?有没有办法兼顾查询缓存的性能和准备好的语句的安全性?


3
老实说 只需继续使用MySQLi。如果已经使用该语句下的准备好的语句工作了,那么PDO基本上是毫无意义的抽象层。编辑:PDO对于不确定哪些数据库要进入后端的绿色领域应用程序非常有用。
jmkeyes 2012年

1
抱歉,我的问题以前不清楚。我已经编辑了 该应用程序目前不在MySQLi中使用准备好的语句;只是mysqli_run_query()。根据我的阅读,MySQLi准备好的语句还绕过了查询缓存。
Andrew Ensley

Answers:


108

要回答您的问题:

  1. MySQL> = 5.1.17(或PREPAREand EXECUTE语句为> = 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;
}

26
回复2:一定值,MySQL的接收作为参数(以本地预处理语句)没有得到解析的SQL 可言?因此,注入的风险必须低于使用PDO的prepare仿真,在这种情况下,转义中的任何缺陷(例如mysql_real_escape_string,多字节字符存在的历史问题)仍然会给注入攻击敞开大门?
eggyal

2
@eggyal,您正在假设如何执行准备好的语句。PDO在其模拟的转义准备转义过程中可能存在错误,但是MySQL也可能存在错误。AFAIK,使用模拟的制备程序未发现任何可能导致参数文字未经转义传递的问题。
弗朗西斯·阿维拉

2
很棒的答案,但是我有一个问题:如果您关闭EMULATION,执行速度会不会变慢?PHP必须将准备好的语句发送给MySQL进行验证,然后才发送参数。因此,如果您使用预处理语句5次,PHP将与MySQL进行6次对话(而不是5次)。这会不会变慢?此外,我觉得有一个更大的机会,PDO可能有错误的验证过程,而不是MySQL的
拉杜Murzea

6
请注意,此答案是使用mysql_real_escape_string引擎盖下的重新编写的语句模拟中提出的要点,以及随之而来的可能出现的漏洞(在非常特殊的情况下)。
eggyal 2013年

6
+1好答案!但为了记录在案,如果您使用本机准备,则即使在MySQL服务器端,也永远不会转义参数或将参数组合到SQL查询中。在执行和提供参数时,查询已被解析并转换为MySQL中的内部数据结构。请阅读由MySQL优化程序工程师解释的过程的博客:guilhembichot.blogspot.com/2014/05/…我并不是说,这意味着本机准备更好,因为我们相信PDO代码可以正确地转义(我做)。
Bill Karwin

9

PDO::ATTR_EMULATE_PREPARESpdo_mysql不对PHP 进行编译时,请注意禁用(启用本机准备)mysqlnd

由于old libmysql与某些功能不完全兼容,因此可能导致奇怪的错误,例如:

  1. 绑定为PDO::PARAM_INT(64位计算机上的0x12345678AB将被裁剪为0x345678AB)时,丢失64位整数的最高有效位
  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上进行了测试,列出的所有bug都在我出现时出现apt-get install php5-mysql,而在我出现时消失apt-get install php5-mysqlnd

PDO::ATTR_EMULATE_PREPARES设置为true(默认) -这些错误不会反正发生,因为PDO未出现在此模式下全部使用准备好的语句。因此,如果您pdo_mysql基于使用libmysql(“ mysqlnd”子字符串未出现pdo_mysql在phpinfo部分的“ Client API version”字段中),则不应PDO::ATTR_EMULATE_PREPARES关闭。


3
这种担忧在2019年仍然有效吗?!
oldboy

8

我将在运行5.1时关闭仿真准备,这意味着PDO将利用本机准备语句功能。

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

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

为了准备好的命名语句和更好的API,我放弃了MySQLi for PDO。

但是,为了达到平衡,PDO的性能要比MySQLi慢得多,但是要牢记这一点。当我做出选择时,我就知道这一点,并决定更好的API和使用行业标准比使用可以忽略的更快的库(将您绑定到特定引擎)更为重要。FWIW,我认为PHP团队也希望在将来通过MySQLi进行PDO。


感谢您提供的信息。无法使用查询缓存如何影响您的性能?或者甚至您以前使用过它?
安德鲁·恩斯利

我不能说我是在多个级别上使用缓存的框架。但是,您始终可以显式使用SELECT SQL_CACHE <语句的其余部分>。
摩根(Morgan)

甚至都不知道有SELECT SQL_CACHE选项。但是,看来这仍然行不通。从文档中:“如果查询结果可缓存,则缓存查询结果...” dev.mysql.com/doc/refman/5.1/en/query-cache-in-select.html
Andrew Ensley,2012年

是。那取决于查询的性质,而不是平台的具体情况。
威尔·摩根(Morgan)

我读到的意思是“ 除非有其他阻止查询结果被缓存的条件,否则查询结果将被缓存 ”,从我之前所读的内容来看,它包括准备好的语句。但是,由于弗朗西斯·阿维拉(Francis Avila)的回答,我知道我的MySQL版本不再适用。
安德鲁·恩斯利

6

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

var_dump($dbh->prepare('INSERT;'));
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
var_dump($dbh->prepare('INSERT;'));

输出

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

我很乐意为实际工作的代码带来性能上的提升。

第一次世界大战

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

MySQL版本:5.5.34-0ubuntu0


这是一个有趣的观点。我猜想仿真会将服务器端解析推迟到执行阶段。虽然没什么大不了的(错误的SQL最终会失败),但让它prepare做应该做的工作更干净。(此外,我一直认为客户端参数解析器必定有其自身的错误。)
ÁlvaroGonzález2014年

1
如果您有兴趣,可以使用IDK,但这我在PDO上发现的一些其他虚假行为的一些文章,这些行为使我开始陷入困境。似乎缺少对多个查询的处理。
quickshiftin

我只是看了GitHub上的一些迁移库...您知道吗,这个迁移库的功能与我的博客文章完全相同。
quickshiftin

5

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

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

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

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


1
无论如何,查询缓存都消失了:从MySQL 5.7.20开始不赞成使用查询缓存,并且在MySQL 8.0中将其删除。
阿尔瓦罗·冈萨雷斯


0

作为记录

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”

无论如何,无论配置如何,十进制值总是返回一个字符串:-(


始终返回十进制值字符串是唯一正确的方法
您的常识

从MySQL的角度来看是可以的,但是在PHP方面却是错误的。Java和C#都将Decimal视为数字值。
magallanes

不,不是。所有这些对于整个计算机科学都是正确的。如果您认为这是错误的,那么您需要另一种精度任意的类型
常识
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.