提问者:小点点

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


这是我到目前为止读到的关于PDO::attr_emulate_prepares的内容:

  1. PDO的prepare仿真性能更好,因为MySQL的本机prepare绕过查询缓存。
  2. MySQL的本机prepare在安全性方面更好(防止SQL注入)。
  3. MySQL的本机prepare更适合错误报告。

我不知道这些说法有多真实了。我在选择MySQL接口时最关心的是防止SQL注入。第二个关注点是业绩。

我的应用程序目前使用过程式MySQLi(没有准备好的语句),并且相当多地利用了查询缓存。它很少在单个请求中重用已准备好的语句。我开始向PDO移动,以获得已准备语句的命名参数和安全性。

我使用的是MySQL 5.1.61PHP 5.3.2

是否应使PDO::attr_emulate_prepares处于启用状态?有没有一种方法可以同时兼顾查询缓存的性能和准备好的语句的安全性?


共2个答案

匿名用户

回答您关心的问题:

>

  • MySQL>=5.1.17(对于prepareexecute语句,>=5.1.21)可以在查询缓存中使用准备好的语句。因此您的MySQL+PHP版本可以在查询缓存中使用准备好的语句。但是,请仔细注意MySQL文档中缓存查询结果的注意事项。有许多类型的查询无法缓存或者即使缓存了也是无用的。根据我的经验,查询缓存并不是一个很大的赢家。查询和模式需要特殊的构造来最大限度地利用缓存。通常,从长远来看,应用程序级缓存最终还是必须的。

    原生准备对安全性没有任何影响。伪准备的语句仍将转义查询参数值,这将只是在PDO库中使用字符串而不是使用二进制协议在MySQL服务器上完成。换句话说,无论您的emulate_prepares设置如何,相同的PDO代码都同样容易(或不容易)受到注入攻击。唯一的区别是参数替换发生在哪里--对于emulate_prepares,它发生在PDO库中;在没有emulate_prepares的情况下,它发生在MySQL服务器上。

    如果没有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;
    }
    

  • 匿名用户

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

    有关更多信息,请参阅该问题的公认答案:PHP+PDO+MySQL:我如何在PHP中从MySQL返回整数和数值列作为整数和数值?。