1. ホーム
  2. php

[解決済み] PDO MySQL。PDO::ATTR_EMULATE_PREPARES を使うか使わないか?

2022-07-31 21:13:09

質問

これは、私がこれまで読んだことのある PDO::ATTR_EMULATE_PREPARES :

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

これらのステートメントのどれがどれだけ真実なのか、もうわかりません。MySQL インターフェースを選択する際の私の最大の関心事は、SQL インジェクションを防止することです。2 番目の懸念はパフォーマンスです。

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

使っているのは MySQL 5.1.61PHP 5.3.2

を残すべきでしょうか。 PDO::ATTR_EMULATE_PREPARES を有効にしておくべきかどうか?クエリ キャッシュのパフォーマンスとプリペアド ステートメントのセキュリティの両方を確保する方法はありますか?

どのように解決するのですか?

お悩みにお答えします。

  1. MySQL >= 5.1.17 (または >= 5.1.21 は PREPAREEXECUTE の文) はクエリキャッシュでプリペアドステートメントを使用することができます。 . つまり、あなたのバージョンのMySQL+PHPは、クエリキャッシュでプリペアドステートメントを使用することができます。しかし、MySQL のドキュメントにあるクエリ結果のキャッシュに関する注意事項をよく覚えておいてください。キャッシュできないクエリや、キャッシュしても意味がないクエリも多くあります。私の経験では、クエリキャッシュはいずれにせよあまり大きな勝利にはならないことが多いです。キャッシュを最大限に活用するためには、クエリやスキーマを特別に構築する必要があります。多くの場合、アプリケーションレベルのキャッシュは、長い目で見れば、いずれにせよ必要なものとなります。

  2. ネイティブのプリペアドは、セキュリティ上何の違いもありません。擬似プリペアド文はクエリパラメータ値をエスケープしますが、それはバイナリプロトコルを使用する MySQL サーバー上ではなく、PDO ライブラリでストリングを使用して行われるだけです。言い換えれば、同じ PDO コードが、あなたの 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;
}