2012-04-11 113 views
97

这是我到目前为止约阅读:PDO MySQL:是否使用PDO :: ATTR_EMULATE_PREPARES?

  1. PDO's prepare emulation is better for performance since MySQL's native prepare bypasses the query cache
  2. MySQL's native prepare is better for security (preventing SQL Injection)
  3. MySQL's native prepare is better for error reporting

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

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

我使用MySQL 5.1.61PHP 5.3.2

我应该离开PDO::ATTR_EMULATE_PREPARES启用与否?有没有办法同时具备查询缓存的性能和预准备语句的安全性?

+3

老实说?只要继续使用MySQLi。如果它已经在使用准备好的语句,PDO基本上是一个毫无意义的抽象层。 *编辑*:PDO对于您不确定哪些数据库将进入后端的绿色领域应用程序非常有用。 – jmkeyes 2012-04-12 01:59:12

+1

对不起,我的问题之前还不清楚。我编辑过它。该应用程序目前不使用MySQLi中的预准备语句;只是mysqli_run_query()。从我读过的,MySQLi准备的语句也绕过了查询缓存。 – 2012-04-12 14:13:01

回答

93

为了回答您的问题:

  1. 的MySQL> = 5.1.17(或> = 5.1.21为PREPAREEXECUTE语句)can use prepared statements in the query cache。所以你的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()可以比发出一个纯文本查询慢一点使用模拟的准备好的语句。在许多数据库系统上,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; 
} 
+20

Re#2:当然,MySQL作为参数(对于本地准备语句)接收的值不会被解析为SQL **吗?因此,注入*的风险必须低于使用PDO的准备模拟,其中任何转义漏洞(例如,历史问题mysql_real_escape_string具有多字节字符)仍然会使注入攻击一开放。 – eggyal 2012-05-04 20:56:00

+0

#1:太棒了!这对我来说是全新的信息,我对此非常高兴。该应用程序确实利用查询缓存很多(超过50%的查询直接从缓存中检索)。 #2:我不得不同意eggyal。移动到准备好的语句的整个目的是为了逃避内容本身的MySQL安全性,而不是PHP猜测内容应该如何逃脱。 #3:我实际上更喜欢在执行查询之前知道有一个问题,所以没关系。很好的答案。谢谢你的帮助! – 2012-05-04 21:35:13

+2

@eggyal,您正在假设如何执行准备好的语句。 PDO可能在仿真准备转义时存在一个错误,但是MySQL也可能有错误。AFAIK,没有发现模拟准备可能导致参数文字通过未转义的问题。 – 2012-05-04 21:39:47

6

当您运行5.1时,我会关闭仿真准备,这意味着PDO将利用本地准备的语句功能。

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

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

我抛弃的MySQLi为PDO的准备命名报表和更好的API。

但是,为了保持平衡,PDO的执行速度比MySQLi慢得多,但需要牢记。当我做出选择时,我知道这一点,并且决定使用更好的API并使用行业标准比使用将您与特定引擎联系起来的可以忽略的更快的库更重要。 FWIW我认为PHP团队也将对PDO和MySQLi的未来表示乐观。

+0

谢谢你的信息。如何无法使用查询缓存会影响您的性能,或者您之前是否使用过? – 2012-05-04 14:10:41

+0

无论如何,我不能说我在多层上使用缓存的框架。尽管你可以总是明确地使用SELECT SQL_CACHE 。 – 2012-05-04 16:37:22

+0

甚至不知道有一个SELECT SQL_CACHE选项。但是,看起来这仍然行不通。从文档:“查询结果缓存**,如果它可缓存** ...”http://dev.mysql.com/doc/refman/5.1/en/query-cache-in-select.html – 2012-05-04 17:25:55

4

我建议你实现实时数据库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

+0

这是一个有趣的观点。我猜测仿真推迟了服务器端解析到执行阶段。虽然这不是什么大不了的事情(错误的SQL最终会失败),但是让'prepare'完成它应该做的工作会更清晰。 (另外,我一直认为客户端参数解析器必然会有它自己的错误。) – 2014-07-15 10:54:59

+1

IDK,如果你有兴趣,但[这里有一点点写法](https://quickshiftin.com/blog/ 2014/07/batching-requests-database-migrations-php-pdo /)对于我注意到的PDO中的一些其他虚假行为,我首先引导了这个兔子洞。似乎缺乏对多个查询的处理。 – quickshiftin 2014-07-15 16:44:45

+0

我只是看着GitHub上的一些迁移库......你知道些什么,[这个](https://github.com/brtriver/dbup/blob/master/src/Dbup/Application.php)与我的博客文章完全一样。 – quickshiftin 2014-07-15 20:13:00

8

谨防上禁用PDO::ATTR_EMULATE_PREPARES(打开本机上做准备)当您的PHP pdo_mysql没有编入mysqlnd时。

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

  1. 时为PDO::PARAM_INT(0x12345678AB将被裁剪到0x345678AB上结合失去最显著位为64位整数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在此模式下根本不使用预处理语句。因此,如果您使用基于libmysqlpdo_mysql(“mysqlnd”子字符串不会出现在phpinfo的pdo_mysql部分的“客户端API版本”字段中) - 您不应该关闭PDO::ATTR_EMULATE_PREPARES