当前位置: 首页 > 知识库问答 >
问题:

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

井疏珂
2023-03-14

这是我到目前为止读到的关于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个答案

经嘉
2023-03-14

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

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

陈开宇
2023-03-14

回答您关心的问题:

>

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

  •  类似资料:
    • 问题内容: 这是我到目前为止阅读的内容: 由于MySQL的本机准备绕过了查询缓存,因此PDO的准备仿真在性能上更好。 MySQL的本机准备更好地提高了安全性(防止SQL注入)。 MySQL的本机准备更好地用于错误报告。 我不知道这些陈述的真实性。在选择MySQL接口时,我最大的担心是防止SQL注入。第二个问题是性能。 我的应用程序当前使用过程MySQLi(没有准备好的语句),并且大量利用了查询缓存

    • 问题内容: 我想使用PDO,但不确定托管是否已正确设置。 如何在PHP中测试它是否已在MySQL中设置并正常工作? 问题答案: 除了使用phpinfo()来查看是否正确列出

    • 在我的应用程序中,我有一个适用于多个用户的通用查询。在某些情况下,用户之间的表结构可能不同。我有一个查询,我只想应用于列存在于表中的用户。

    • 问题内容: 我想要一个条件,以防该行根本不存在。 尝试过,但没有一个起作用。 问题答案: 您可以直接检查返回值。 如果您询问是否进行检查 而不进行获取, 则只需让MySQL返回a (或使用命令)。

    • 我正在设置一个函数,以检查传递的用户名是否存在于我数据库的表中。为了做到这一点,我使用了以下代码: 然而,无论我尝试设置为,我都无法获得任何

    • 我正在处理的目标系统不支持PDO ist,尽管我在Postgres-DB8.2+上使用PHP 5.1.x寻求防止SQL注入的解决方案。目前没有机会切换到PDO。 我目前的解决方案是pg_prepare-prepared语句: 该函数从查询字符串创建一个名为stmtname的准备好的语句,该语句必须包含单个SQL命令。stmtname可以是“”来创建未命名语句,在这种情况下,任何先前存在的未命名语句