PDO MySQL:使用PDO :: ATTR_EMULATE_PREPARES还是不行?

这是迄今为止我读到的有关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启用或不? 有没有办法同时具有查询缓存的性能和预准备语句的安全性?

回答您的疑虑:

  1. 对于PREPAREEXECUTE语句,MySQL> = 5.1.17(或> = 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()和native预处理语句可能比使用模拟预处理语句发出纯文本查询慢一点。 在许多数据库系统上, 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; } 

当您的PHP pdo_mysql不针对mysqlnd编译时,请注意禁用PDO::ATTR_EMULATE_PREPARES (将本机准备就绪)。

由于旧的libmysql与某些函数不完全兼容,可能导致奇怪的错误,例如:

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

这些错误我在我简单的项目中找到了,当迁移到其他服务器使用libmysqlpdo_mysql模块。 也许有更多的错误,我不知道。 我也测试了新鲜的64位debian jessie,所有列出的错误发生在我apt-get install php5-mysql ,并且当我apt-get install php5-mysqlnd

PDO::ATTR_EMULATE_PREPARES设置为true(默认)时,这些错误不会发生,因为PDO在此模式下根本不使用预处理语句。 因此,如果您使用基于libmysql pdo_mysql(“phpinfo”中的“mysqlnd”子字符串不会出现在pdo_mysql部分的“客户端API版本”字段中) – 您不应该关闭PDO::ATTR_EMULATE_PREPARES

我将在运行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团队也将对PDO和MySQLi的未来也有好感。

我建议启用真正的数据库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) 

我很乐意为实际工作的代码提供性能。

FWIW

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

MySQL版本:5.5.34-0ubuntu0