PDO MySQL:PDO :: ATTR_EMULATE_PREPARESを使用するかどうか。


117

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

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

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

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

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

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


3
正直なところ?MySQLiを使い続けるだけです。その下で準備さ​​れたステートメントを使用してすでに機能している場合、PDOは基本的に無意味な抽象化レイヤーです。編集:PDOは、どのデータベースがバックエンドに入るのかわからないグリーンフィールドアプリケーションに非常に役立ちます。
jmkeyes

1
申し訳ありませんが、私の質問は以前は不明確でした。編集しました。現在、アプリケーションはMySQLiで準備済みステートメントを使用していません。mysqli_run_query()だけです。私が読んだことから、MySQLiの準備済みステートメントもクエリキャッシュをバイパスします。
Andrew Ensley

回答:


108

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

  1. MySQL> = 5.1.17(またはPREPAREand EXECUTEステートメントの場合は> = 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;
}

26
再#2:MySQLが(ネイティブの準備されたステートメントへの)パラメータとして受け取る値は、SQL に対してまったく解析されませんか?したがって、インジェクションのリスクは、PDOの準備エミュレーションを使用する場合よりも低くなければなりません。エスケープの欠陥(たとえば、mysql_real_escape_stringマルチバイト文字に関する歴史的な問題)によって、インジェクション攻撃にさらされる可能性がありますか?
egyal

2
@eggyal、あなたは準備されたステートメントがどのように実装されるかについて仮定をしています。PDOはエミュレートされた準備のエスケープにバグがあるかもしれませんが、MySQLにもバグがあるかもしれません。私の知る限り、エミュレートされた準備で問題が発見されておらず、パラメータリテラルがエスケープされずに通過する可能性があります。
フランシスアビラ

2
すばらしい答えですが、質問があります。エミュレーションをオフにすると、実行速度が遅くなりますか?PHPは、検証のために準備されたステートメントをMySQLに送信し、その後にのみパラメーターを送信する必要があります。したがって、準備されたステートメントを5回使用すると、PHPはMySQLと(5回ではなく)6回通信します。これは遅くなりますか?さらに、PDOがMySQLではなく検証プロセスにバグを含む可能性が高いと思います...
Radu Murzea

6
この回答で述べたポイントは、内部で使用mysql_real_escape_stringしたステートメントエミュレーションと、結果として生じる可能性のある脆弱性(非常に特殊なエッジケース)を再作成したことに注意してください。
eggyal 2013年

6
+1正解です。ただし、レコードについては、ネイティブ準備を使用する場合、MySQLサーバー側でもパラメーターがエスケープされたり、SQLクエリに結合されたりすることはありません。パラメータを実行して指定するまでに、クエリは解析され、MySQLの内部データ構造に変換されます。このプロセスを説明するMySQLオプティマイザエンジニアがこのブログを読んでください:guilhembichot.blogspot.com/2014/05/…これは、PDOコードが正しくエスケープすることを信頼している限り、ネイティブの準備が優れていることを意味しているわけではありません(行う)。
ビルカーウィン2014

9

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

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

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

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

ときにPDO::ATTR_EMULATE_PREPARES(デフォルトで)trueに設定されている- PDOは、このモードでは、すべてのプリペアドステートメントを使用していないため、これらのバグは、とにかく発生しません。したがって、(「mysqlnd」サブストリングがphpinfo のセクションの「クライアントAPIバージョン」フィールドに表示されない)にpdo_mysql基づいて使用する場合、オフにしないでください。libmysqlpdo_mysqlPDO::ATTR_EMULATE_PREPARES


3
この懸念は2019年でも有効ですか?
oldboy '30 / 07/30

8

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に好意的に取り組んでいると思います。


その情報をありがとう。クエリキャッシュを使用できなかったことがパフォーマンスにどのように影響しましたか、それとも以前に使用していましたか?
Andrew Ensley、2012年

とにかく、複数のレベルでキャッシュを使用しているフレームワークとは言えません。ただし、常に明示的にSELECT SQL_CACHE <ステートメントの残り>を使用できます。
モーガンは

SELECT SQL_CACHEオプションがあることさえ知らなかった。しかし、それでもうまくいかないようです。ドキュメントから:「キャッシュ可能な場合、クエリ結果はキャッシュされます...」dev.mysql.com/doc/refman/5.1/en/query-cache-in-select.html
Andrew Ensley

はい。これは、プラットフォームの詳細ではなく、クエリの性質に依存します。
モーガンは

それは、「何か他のものがそれをキャッシュ可能にしない限り、クエリ結果はキャッシュされる」という意味で読んだ。ただし、Francis Avilaの回答のおかげで、MySQLのバージョンではそれが当てはまらないことがわかりました。
Andrew Ensley、2012年

6

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


それは興味深い点です。エミュレーションはサーバー側の解析を実行フェーズに延期すると思います。それは大した問題ではありませんが(間違ったSQLは最終的に失敗します)、prepare本来の仕事をさせるほうがきれいです。(さらに、クライアント側のパラメーターパーサーには必ず独自のバグがあると私は常に想定していました。)
ÁlvaroGonzález'15 /

1
興味があればIDKですが、ここでは、私がPDOで気付いた、最初にこのうさぎの穴を掘り下げる他のいくつかの偽の振る舞いについて少し説明します。複数のクエリの処理が不足しているようです。
quickshiftin 2014

GitHubでいくつかのマイグレーションライブラリを見ただけです...何を知っていますか。これは、私のブログの投稿とまったく同じことを行います。
quickshiftin 2014

5

エミュレーションを「false」に切り替える理由

これの主な理由は、PDOの代わりにデータベースエンジンが準備を行うようにすることは、クエリと実際のデータが別々に送信されるため、セキュリティが向上するためです。これは、MySQLの準備されたステートメントが単一のクエリに制限されているため、パラメーターがクエリに渡されると、SQLをパラメーターに挿入する試みがブロックされることを意味します。つまり、パラメータで2番目のクエリを渡すと、真の準備済みステートメントは失敗します。

準備とPDOにデータベースエンジンを使用することに対する主な議論は、サーバーへの2回のトリップです。1つは準備のため、もう1つはパラメーターを渡すためです。また、少なくともMySQLの場合、バージョン5.1以降、クエリキャッシュは問題になりません。

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


1
とにかく、クエリキャッシュはなくなりました。クエリキャッシュはMySQL 5.7.20で非推奨になり、MySQL 8.0では削除されました。
アルバロゴンサレス

5

エミュレーションをオフにする最大の理由の1つに言及している人がいないことに驚いています。エミュレーションをオンにすると、PDOはすべての整数と浮動小数点数を文字列として返します。エミュレーションをオフにすると、MySQLの整数と浮動小数点がPHPの整数と浮動小数点になります。

詳細については、この質問の受け入れられた回答を参照してください:PHP + PDO + MySQL:PHPで整数および数値としてMySQLから整数および数値列を返すにはどうすればよいですか?


0

記録のために

PDO :: ATTR_EMULATE_PREPARES = true

厄介な副作用が発生する可能性があります。これは、int値を文字列として返すことができます。

PHP 7.4、mysqlndを使用したpdo。

PDO :: ATTR_EMULATE_PREPARES = trueでクエリを実行する

列:id
タイプ:整数
値:1

PDO :: ATTR_EMULATE_PREPARES = falseでクエリを実行する

列:id
タイプ:文字列
値: "1"

いずれの場合も、構成に関係なく、10進数値は常に文字列で返されます:-(


10進値は常に返されます。文字列が唯一の正しい方法です
常識

MySQLの観点からはそうですが、PHP側では間違っています。JavaとC#はどちらもDecimalを数値と見なします。
magallanes

いいえ、そうではありません。コンピュータサイエンス全体にとって正しいことです。それが間違っていると思われる場合は、任意の精度の別のタイプが必要です
常識
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.