127

これは私がこれまで読んだことですPDO::ATTR_EMULATE_PREPARES

  1. PDO の準備エミュレーションは、MySQL のネイティブの準備がクエリ キャッシュをバイパスするため、パフォーマンスが優れています
  2. MySQL のネイティブの準備は、セキュリティ (SQL インジェクションの防止) に優れています
  3. MySQL のネイティブの準備は、エラー報告に適しています

これらの声明がどれほど真実であるかは、もはやわかりません。MySQL インターフェイスを選択する際の私の最大の関心事は、SQL インジェクションを防ぐことです。2 番目の懸念事項はパフォーマンスです。

私のアプリケーションは現在、手続き型の MySQLi (プリペアド ステートメントなし) を使用しており、クエリ キャッシュをかなり利用しています。単一のリクエストで準備済みステートメントを再利用することはめったにありません。名前付きパラメーターと準備済みステートメントのセキュリティのために、PDO への移行を開始しました。

私は使用MySQL 5.1.61していますPHP 5.3.2

有効のままにPDO::ATTR_EMULATE_PREPARESする必要がありますか? クエリ キャッシュのパフォーマンスと準備済みステートメントのセキュリティの両方を実現する方法はありますか?

4

8 に答える 8

115

あなたの懸念に答えるために:

  1. MySQL> = 5.1.17(またはPREPAREandEXECUTEステートメントの場合は> = 5.1.21)は、クエリキャッシュでプリペアドステートメントを使用できます。したがって、MySQL + PHPのバージョンでは、クエリキャッシュでプリペアドステートメントを使用できます。ただし、MySQLドキュメントでクエリ結果をキャッシュする際の注意事項に注意してください。キャッシュできないクエリや、キャッシュしても役に立たないクエリにはさまざまな種類があります。私の経験では、クエリキャッシュは、とにかくそれほど大きな勝利にはならないことがよくあります。クエリとスキーマは、キャッシュを最大限に活用するために特別な構築が必要です。多くの場合、アプリケーションレベルのキャッシュは、長期的にはとにかく必要になります。

  2. ネイティブの準備はセキュリティに何の違いもありません。疑似プリペアドステートメントは引き続きクエリパラメータ値をエスケープします。バイナリプロトコルを使用するMySQLサーバーではなく、文字列を使用してPDOライブラリで実行されます。つまり、同じPDOコードは、設定に関係なく、インジェクション攻撃に対して同等に脆弱(または脆弱ではない)になりますEMULATE_PREPARES。唯一の違いは、パラメータの置換が発生する場所です。を使用EMULATE_PREPARESすると、PDOライブラリで発生します。がないEMULATE_PREPARESと、MySQLサーバーで発生します。

  3. これがないEMULATE_PREPARESと、実行時ではなく準備時に構文エラーが発生する可能性があります。EMULATE_PREPARESPDOには実行時までMySQLに提供するクエリがないため、実行時にのみ構文エラーが発生します。これはあなたが書くコードに影響を与えることに注意してください!特に使用している場合PDO::ERRMODE_EXCEPTION

追加の考慮事項:

  • prepare()(ネイティブプリペアドステートメントを使用する)には固定コストがあるため、ネイティブプリペアドステートメントを使用prepare();execute()する場合は、エミュレートされたプリペアドステートメントを使用してプレーンテキストクエリを発行するよりも少し遅くなる可能性があります。多くのデータベースシステムでは、aのクエリプラン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;
}
于 2012-05-04T19:48:39.450 に答える
10

PHPが に対してコンパイルされていないPDO::ATTR_EMULATE_PREPARES場合は、無効にする (ネイティブ準備をオンにする)ことに注意してください。pdo_mysqlmysqlnd

oldlibmysqlは一部の機能と完全に互換性がないため、次のような奇妙なバグが発生する可能性があります。

  1. バインド時に 64 ビット整数の最上位ビットが失われるPDO::PARAM_INT(64 ビット マシンでは 0x12345678AB が 0x345678AB にトリミングされます)
  2. のような単純なクエリを作成できない(例外LOCK TABLESをスローする)SQLSTATE[HY000]: General error: 2030 This command is not supported in the prepared statement protocol yet
  3. 結果からすべての行をフェッチするか、次のクエリの前にカーソルを閉じる必要があります (mysqlndまたはエミュレートされた準備を使用すると、この作業が自動的に実行され、mysql サーバーと同期しなくなります)

モジュールに使用libmysqlされる他のサーバーに移行したときに、単純なプロジェクトで見つけたこれらのバグ。pdo_mysqlたぶん、もっと多くのバグがあるかもしれませんが、私にはわかりません。また、新しい 64 ビット debian jessie でテストしました。リストされているすべてのバグは、 I のときに発生し、 Iapt-get install php5-mysqlのときに消えapt-get install php5-mysqlndます。

PDO::ATTR_EMULATE_PREPAREStrue に設定されている場合 (デフォルト) - PDO はこのモードでは準備済みステートメントをまったく使用しないため、これらのバグは発生しません。そのため、pdo_mysqlベースlibmysql(「mysqlnd」部分文字列pdo_mysqlが phpinfo のセクションの「クライアント API バージョン」フィールドに表示されない)を使用する場合は、PDO::ATTR_EMULATE_PREPARESオフにしないでください。

于 2016-09-29T19:36:22.767 に答える
9

5.1 を実行しているので、エミュレート準備をオフにします。つまり、PDO はネイティブのプリペアド ステートメント機能を利用します。

PDO_MYSQL は、MySQL 4.1 以降に存在するネイティブのプリペアド ステートメント サポートを利用します。古いバージョンの mysql クライアント ライブラリを使用している場合、PDO がそれらをエミュレートします。

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

準備された名前付きステートメントとより優れた API のために、PDO 用の MySQLi を捨てました。

ただし、バランスをとるために、PDO のパフォーマンスは MySQLi よりも無視できるほど遅くなりますが、これは心に留めておくべきことです。選択をしたときにこれを知っていたので、特定のエンジンに結びつく無視できるほど高速なライブラリを使用するよりも、より優れた API と業界標準を使用することが重要であると判断しました。FWIW PHP チームも、将来的には MySQLi よりも PDO を好意的に見ていると思います。

于 2012-05-04T09:06:32.937 に答える
7

エミュレーションはすべてをキャッチするわけではないため、実際のデータベース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

于 2014-07-15T05:27:44.303 に答える
0

複数のバインドされたパラメーターがある場合、最初のことは間違いなく当てはまります。11 個のパラメーターを持つ SQL があり、エミュレートされた準備なしで 5 秒かかりました。エミュレートされた準備を有効にすると、0.25 秒に短縮されました。

PHP 7.4.11 では同様の問題https://bugs.php.net/bug.php?id=80027が解決されるはずでしたが、PHP 7.4.27 にアップグレードした後も問題は解決しませんでした。

于 2021-12-27T18:53:24.950 に答える