web-dev-qa-db-ja.com

「SET NAMES」を使用するかどうか

O'Reillyの "High performance MySQL"を読んで、次のことに出くわしました

別の一般的なガベージクエリはSET NAMES UTF8です。これはとにかく間違った方法です(クライアントライブラリの文字セットは変更されません。サーバーのみに影響します)。

私は少し混乱してしまいました。これは、すべてのスクリプトの先頭に "SET NAMES utf8"を配置して、クエリにutf8がエンコードされていることをdbに知らせるために使用したためです。

誰でも上記の引用にコメントすることができますか、より正式に言えば、私のデータベースワークフローがユニコード対応であることを確認するための提案/ベストプラクティスは何ですか?.

私のターゲット言語はphpで、これが関連する場合はpythonです。

60
user187291

mysql_set_charset() はオプションですが、オプションは ext/mysql に限定されます。 ext/mysqli の場合 mysqli_set_charset で、 PDO::mysqlの場合接続パラメーターを指定します。

この関数を使用するとMySQL API呼び出しが発生するため、クエリを発行するよりもはるかに高速であると見なす必要があります。

パフォーマンスに関して、スクリプトとMySQLサーバー間のUTF-8ベースの通信を保証する最速の方法は、MySQLサーバーを正しく設定することです。 SET NAMES x同等

SET character_set_client = x;
SET character_set_results = x;
SET character_set_connection = x;

SET character_set_connection = xは内部でSET collation_connection = <<default_collation_of_character_set_x>>も実行しますが、my.ini/cnfこれらのサーバー変数 を静的に設定することもできます。

同じMySQLサーバーインスタンスで実行され、他の文字セットを必要とする他のアプリケーションで発生する可能性のある問題に注意してください。

30
Stefan Gehrig

TLDR

// The key is the "charset=utf8" part.
$dsn = 'mysql:Host=localhost;dbname=testdb;charset=utf8';
$dbh = new PDO($dsn, 'user', 'pass');

この答えは、phpのpdoライブラリに重点を置いています。これは、非常に遍在しているためです。

簡単なリマインダー-mysqlはクライアントサーバーアーキテクチャです。これは重要です。実際のデータベースがあるmysqlサーバーだけでなく、mysqlサーバーと通信する別のmysqlクライアントドライバーもあるからです(それらは別個のエンティティです)。 mysqlクライアントとpdoが混在していると言ってもいいでしょう。

set names utf8を使用すると、標準のsqlクエリをmysqlに発行します。 sqlクエリはpdoを通過し、mysqlクライアントライブラリを通過し、最終的にmysqlサーバーに到達しますが、mysqlサーバーのみがそのsqlクエリを解析および解釈します。 mysqlサーバーがpdoにメッセージを返送しないか、mysqlクライアントに文字セットとエンコードが変更されたことを通知するため、これは重要です。したがって、mysqlクライアントとpdoはどちらも、それが発生したという事実にまったく無知です。

クライアントライブラリは、現在の文字セットを認識していない場合、文字列を適切に処理できないため、これを行わないことが重要です。ほとんどの一般的な操作は、クライアントが正しい文字セットを知らなくても正しく動作しますが、 PDO :: quote などの文字列エスケープはありません。プリペアドステートメントを使用しているため、このような手動のプリミティブ文字列のエスケープについて心配する必要はないと思うかもしれませんが、pdo:mysqlユーザーの大部分は、知らないうちに エミュレートプリペアドステートメント を使用しているためです非常に長い間、pdo:mysqlドライバーのデフォルト設定。エミュレートされたプリペアドステートメントは、mysql apiによって提供される実際のネイティブmysqlプリペアドステートメントを使用しません。代わりに、phpはすべての値に対してPDO::quote()を呼び出し、すべてのプレースホルダーを引用符で囲まれた値にstr_replacingするのと同じことを行います。

使用している文字セットがわからなければ文字列を適切にエスケープできないため、これらのエミュレートされた準備済みステートメントは、set namesを介して特定の文字セットに変更した場合、SQLインジェクションに対して脆弱です。 SQLインジェクションの可能性に関係なく、別の文字セット向けのエスケープスキームを使用すると、文字列を分割できます。

Pdo mysqlドライバーの場合、接続時に文字セットを指定できます DSNで指定 。これを行うと、クライアントライブラリとサーバーの両方で文字セットが認識されるため、物事は期待どおりに機能します。

// The key is the "charset=utf8" part.
$dsn = 'mysql:Host=localhost;dbname=testdb;charset=utf8';
$dbh = new PDO($dsn, 'user', 'pass');

しかし、不適切な文字列エスケープだけが問題ではありません。たとえば、列名が文字列として指定されているため、 PDO :: bindColumn の使用にも問題が発生する可能性があります。例として、ütube(ウムラウトに注意)という名前の列名があり、セット名を介してlatinからutf8に切り替えてから、$stmt->bindColumn('ütube', $var);を試してみます。 ütubeはutf8エンコードされた文字列です。これは、phpファイルがutf8エンコードされているためです。それは機能しません。文字列をlatin1バリアントとしてエンコードする必要があります...そして今、あらゆる種類のクレイジーが進行しています。

26
goat

Pyについてはわかりませんが、phpには mysql_set_charset があります。これは、「これは、文字セットを変更する好ましい方法であり、mysql_query()を使用してSET NAMESを実行することは推奨されません」 」この関数はMySQL 5.0.7で導入されたため、以前のバージョンでは機能しないことに注意してください。

mysql_set_charset('utf8', $link);

$ linkはmysql_connectで作成された接続です

9
typeoneerror