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

时间:2012-04-11 20:37:34

标签: php mysql pdo

到目前为止,这是关于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
  4. 我不知道这些陈述是多么真实。选择MySQL接口时我最担心的是阻止SQL注入。第二个问题是表现。

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

    我正在使用MySQL 5.1.61PHP 5.3.2

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

7 个答案:

答案 0 :(得分:101)

回答您的疑虑:

  1. MySQL> = 5.1.17(或PREPAREEXECUTE语句的> = 5.1.21)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

  4. 另一个考虑因素:

    • 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;
    }
    

答案 1 :(得分:9)

当您的PHP PDO::ATTR_EMULATE_PREPARES未针对pdo_mysql进行编译时,请注意禁用mysqlnd(启用本机准备工作)。

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

  1. 绑定为PDO::PARAM_INT时丢失64位整数的最高有效位(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服务器不同步)
  4. 我迁移到使用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(&#34; mysqlnd&#34;子字符串确实出现在&#34;客户端API版本&#34; {{1}字段在phpinfo中的部分) - 你不应该关闭pdo_mysql

答案 2 :(得分:7)

当你运行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也非常看好PDO。

答案 3 :(得分:5)

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

答案 4 :(得分:3)

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

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

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

答案 5 :(得分:2)

我很惊讶,没有人提到关闭仿真的最大原因之一。启用仿真后,PDO将返回所有整数,并以 strings 的形式进行浮点运算。关闭仿真后,MySQL中的整数和浮点数将变为PHP中的整数和浮点数。

有关更多信息,请参见以下问题的可接受答案:PHP + PDO + MySQL: how do I return integer and numeric columns from MySQL as integers and numerics in PHP?

答案 6 :(得分:2)

记录

PDO :: ATTR_EMULATE_PREPARES = true

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

PHP 7.4,带有mysqlnd的pdo。

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

列:id
类型:整数
值:1

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

列:id
类型:string
值:“ 1”

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