web-dev-qa-db-ja.com

PHPとMySQLの使用に最適な照合順序は何ですか?

MySQLには、入力内容が100%わからない一般的なWebサイトの照合順序として「最良の」選択があるのではないかと思います。 MySQL、Apache、HTML、そしてPHP内部のものなど、すべてのエンコーディングは同じであるべきだと私は理解しています。

これまで、私はPHPを "UTF-8"で出力するように設定していましたが、MySQLではこれとどの照合が一致しますか?私はそれがUTF-8の1つであると思っています、しかし私は以前にutf8_unicode_ciutf8_general_ci、そしてutf8_binを使いました。

685
Darryl Hein

主な違いは、ソートの正確さ(言語の文字を比較するとき)とパフォーマンスです。唯一の特別なものは、バイナリ形式の文字を比較するためのutf8_binです。

utf8_general_ciutf8_unicode_ciよりやや速いですが、(ソートのために)正確さは劣ります。 特定言語のutf8エンコーディングutf8_swedish_ciなど)には、それらをそれらの言語のソートに最も正確にする追加の言語規則が含まれています。特定の言語を好む理由がない限り、ほとんどの場合、私はutf8_unicode_ciを使用します(パフォーマンスのわずかな向上よりも精度を優先します)。

MySQLのマニュアルで特定のUnicode文字セットについてもっと読むことができます - http://dev.mysql.com/doc/refman/5.0/en/charset-unicode-sets.html

577
Eran Galperin

utf8_general_ciを使用しているときに発生する可能性があるこの問題を非常によく認識してください。

utf8_general_ci照合が使用されている場合、MySQLはselectステートメント内のいくつかの文字を区別しません。これは非常に厄介なバグを引き起こす可能性があります - 特に、例えば、ユーザ名が関係している場合。データベーステーブルを使用する実装によっては、この問題により悪意のあるユーザーが管理者アカウントと一致するユーザー名を作成する可能性があります。

この問題は、少なくとも5.xの初期のバージョンでは最低限現れています - この振る舞いが後で変更されたのかどうかはわかりません。

私はDBAではありませんが、この問題を回避するために、大文字と小文字を区別しないで常にutf8-binを使用します。

以下のスクリプトは、問題を例で説明しています。

-- first, create a sandbox to play in
CREATE DATABASE `sandbox`;
use `sandbox`;

-- next, make sure that your client connection is of the same 
-- character/collate type as the one we're going to test next:
charset utf8 collate utf8_general_ci

-- now, create the table and fill it with values
CREATE TABLE `test` (`key` VARCHAR(16), `value` VARCHAR(16) )
    CHARACTER SET utf8 COLLATE utf8_general_ci;

INSERT INTO `test` VALUES ('Key ONE', 'value'), ('Key TWO', 'valúe');

-- (verify)
SELECT * FROM `test`;

-- now, expose the problem/bug:
SELECT * FROM test WHERE `value` = 'value';

--
-- Note that we get BOTH keys here! MySQLs UTF8 collates that are 
-- case insensitive (ending with _ci) do not distinguish between 
-- both values!
--
-- collate 'utf8_bin' doesn't have this problem, as I'll show next:
--

-- first, reset the client connection charset/collate type
charset utf8 collate utf8_bin

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Note that we get just one key now, as you'd expect.
--
-- This problem appears to be specific to utf8. Next, I'll try to 
-- do the same with the 'latin1' charset:
--

-- first, reset the client connection charset/collate type
charset latin1 collate latin1_general_ci

-- next, convert the values that we've previously inserted
-- in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET latin1 COLLATE latin1_general_ci;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Again, only one key is returned (expected). This shows 
-- that the problem with utf8/utf8_generic_ci isn't present 
-- in latin1/latin1_general_ci
--
-- To complete the example, I'll check with the binary collate
-- of latin1 as well:

-- first, reset the client connection charset/collate type
charset latin1 collate latin1_bin

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET latin1 COLLATE latin1_bin;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Again, only one key is returned (expected).
--
-- Finally, I'll re-introduce the problem in the exact same 
-- way (for any sceptics out there):

-- first, reset the client connection charset/collate type
charset utf8 collate utf8_generic_ci

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;

-- now, re-check for the problem/bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Two keys.
--

DROP DATABASE sandbox;
114
Guus

実際には、おそらくutf8_unicode_ciutf8_general_ciを使いたいでしょう。

  • utf8_general_ciはすべてのアクセントを取り除き、あたかもそれがASCIIであるかのようにソートすることによってソートします。
  • utf8_unicode_ciはUnicodeのソート順を使用するので、より多くの言語で正しくソートされます。

ただし、これを英語のテキストの保存にのみ使用しているのであれば、違いはありません。

111
Vegard Larsen

照合utf8mb4と一緒に文字セットutf8mb4_unicode_ciを使用するのが最善です。

文字セットutf8は、少量のUTF-8コードポイント(可能な文字の約6%)しかサポートしていません。 utf8はBasic Multilingual Plane(BMP)のみをサポートします。他に16の飛行機があります。各面は65,536文字です。 utf8mb4は17のプレーンすべてをサポートします。

MySQLは4バイトのUTF-8文字を切り捨てるため、データが破損します。

utf8mb4文字セットは、2010-03-24のMySQL 5.5.3で導入されました。

新しい文字セットを使用するために必要な変更のいくつかは簡単ではありません。

  • アプリケーションデータベースアダプタに変更を加える必要があるかもしれません。
  • 文字セットの設定、照合順序、innodb_file_formatのBarracudaへの切り替えなど、my.cnfに変更を加える必要があります。
  • SQL CREATEステートメントには、次のものを含める必要があります。ROW_FORMAT=DYNAMIC
    • DYNAMICはVARCHAR(192)以上の索引に必要です。

注:BarracudaからAntelopeに切り替えるには、MySQLサービスを複数回再起動する必要があります。 innodb_file_format_maxは、MySQLサービスがinnodb_file_format = barracudaに再起動されるまで変わりません。

MySQLは古いAntelope InnoDBファイルフォーマットを使用します。 Barracudaは動的行フォーマットをサポートしています。これは、文字セットに切り替えた後にインデックスとキーを作成するためのSQLエラーに遭遇したくない場合に必要になります。utf8mb4

  • #1709 - インデックスカラムサイズが大きすぎる。最大列サイズは767バイトです。
  • #1071 - 指定されたキーが長すぎました。最大キー長は767バイトです

次のシナリオはMySQL 5.6.17でテストされています。デフォルトでは、MySQLは次のように構成されています。

SHOW VARIABLES;

innodb_large_prefix = OFF
innodb_file_format = Antelope

MySQLサービスを停止して、既存のmy.cnfにオプションを追加します。

[client]
default-character-set= utf8mb4

[mysqld]
explicit_defaults_for_timestamp = true
innodb_large_prefix = true
innodb_file_format = barracuda
innodb_file_format_max = barracuda
innodb_file_per_table = true

# Character collation
character_set_server=utf8mb4
collation_server=utf8mb4_unicode_ci

SQL CREATEステートメントの例

CREATE TABLE Contacts (
 id INT AUTO_INCREMENT NOT NULL,
 ownerId INT DEFAULT NULL,
 created timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
 modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 contact VARCHAR(640) NOT NULL,
 prefix VARCHAR(128) NOT NULL,
 first VARCHAR(128) NOT NULL,
 middle VARCHAR(128) NOT NULL,
 last VARCHAR(128) NOT NULL,
 suffix VARCHAR(128) NOT NULL,
 notes MEDIUMTEXT NOT NULL,
 INDEX IDX_CA367725E05EFD25 (ownerId),
 INDEX created (created),
 INDEX modified_idx (modified),
 INDEX contact_idx (contact),
 PRIMARY KEY(id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB ROW_FORMAT=DYNAMIC;
  • ROW_FORMAT=DYNAMICがCREATEステートメントから削除されている場合、INDEX contact_idx (contact)に対してエラー#1709が生成されているのがわかります。

注:contactの最初の128文字に制限するようにインデックスを変更すると、ROW_FORMAT=DYNAMICでBarracudaを使用するための要件が​​なくなります。

INDEX contact_idx (contact(128)),

また、注意してください:それはフィールドのサイズがVARCHAR(128)であると言うとき、それは128バイトではありません。 128文字、4バイト文字、または128文字、1バイト文字を使用できます。

このINSERTステートメントは、2行目に4バイトの「poo」文字を含める必要があります。

INSERT INTO `Contacts` (`id`, `ownerId`, `created`, `modified`, `contact`, `prefix`, `first`, `middle`, `last`, `suffix`, `notes`) VALUES
(1, NULL, '0000-00-00 00:00:00', '2014-08-25 03:00:36', '1234567890', '12345678901234567890', '1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890', '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678', '', ''),
(2, NULL, '0000-00-00 00:00:00', '2014-08-25 03:05:57', 'poo', '12345678901234567890', '????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '', ''),
(3, NULL, '0000-00-00 00:00:00', '2014-08-25 03:05:57', 'poo', '12345678901234567890', '????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '123????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '', '');

last列によって使用されているスペースの量を確認できます。

mysql> SELECT BIT_LENGTH(`last`), CHAR_LENGTH(`last`) FROM `Contacts`;
+--------------------+---------------------+
| BIT_LENGTH(`last`) | CHAR_LENGTH(`last`) |
+--------------------+---------------------+
|               1024 |                 128 | -- All characters are ASCII
|               4096 |                 128 | -- All characters are 4 bytes
|               4024 |                 128 | -- 3 characters are ASCII, 125 are 4 bytes
+--------------------+---------------------+

データベースアダプタでは、接続の文字セットと照合順序を設定します。

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'

PHPでは、これは次のように設定されます。\PDO::MYSQL_ATTR_INIT_COMMAND

参考文献:

77

照合順序は、データの並べ替え方法と文字列の比較方法に影響します。つまり、ほとんどのユーザーが期待する照合順序を使用する必要があります。

ドキュメンテーション :からの例

utf8_general_ciもドイツ語とフランス語の両方に適していますが、「ß」は「s」と等しく、「ss」とは異なります。これがあなたのアプリケーションにとって許容できるものであるなら、それは速いのでutf8_general_ciを使うべきです。それ以外の場合は、utf8_unicode_ciを使用してください。

だから - それはあなたの予想されるユーザベースとあなたが必要とする量によります 正しい ソーティング。英語のユーザーベースでは、utf8_general_ciで十分です。スウェーデン語などの他の言語では、特別な照合が作成されています。

41
Tomalak

基本的に、それはあなたが弦をどう思うかによって異なります。

Guusが強調している問題のため、私はいつもutf8_binを使います。私の意見では、データベースに関する限り、文字列はまだ単なる文字列です。文字列はUTF-8文字の数です。文字はバイナリ表現をしていますが、なぜあなたは使用している言語を知る必要があるのでしょうか?通常、人々は多言語サイトの範囲を持つシステム用のデータベースを構築します。これがUTF-8を文字セットとして使用する全体的なポイントです。私はちょっとした純粋主義者ですが、バグのリスクがインデックス作成で得られるわずかな利点よりもかなり大きいと思います。言語関連の規則はすべて、DBMSよりはるかに高いレベルで実行する必要があります。

私の本では、「価値」は100万年以内に「価値」と等しくなるべきではありません。

テキストフィールドを格納して大文字と小文字を区別しない検索をしたい場合は、LOWER()やphp関数strtolower()などのPHP関数を含むMYSQL文字列関数を使用します。

22
Phil

UTF-8テキスト情報の場合は、utf8_general_ciを使用する必要があります。

  • utf8_bin:文字列の各文字のバイナリ値で文字列を比較します

  • utf8_general_ci:一般的な言語の規則と大文字と小文字を区別しない比較を使って文字列を比較する

それは、データの検索と索引付けをより速く/より効率的に/より便利にするでしょう。

12
mepcotterell

受け入れられた答えはかなり明確にutf8_unicode_ciを使用することを示唆しています、そして新しいプロジェクトのためにすばらしい間、私はそれが誰かが時間を節約する念のために私の最近の反対の経験を関連づけたいと思いました。

Utf8_general_ciはMySQLのUnicodeのデフォルトの照合順序なので、utf8_unicode_ciを使用する場合は、 lot の場所で指定する必要があります。

たとえば、すべてのクライアント接続にはデフォルトの文字セット(私にとっては意味があります)だけでなく、デフォルトの照合順序(つまり、Unicodeの場合は照合順序は常にutf8_general_ciにデフォルト設定されています)もあります。

おそらく、フィールドにutf8_unicode_ciを使用する場合は、データベースに接続するスクリプトを更新して目的の照合順序を明示的に指定する必要があります。そうしないと、接続にデフォルトの照合順序が使用されているとテキスト文字列を使用したクエリが失敗します。

その結果、任意のサイズの既存のシステムをUnicode/utf8に変換するとき、MySQLがデフォルトを処理する方法のために、utf8_general_ciを使用することを強いられる可能性があります。

10
George Lund

Guusが強調しているケースでは、utf8_bin(厳密なマッチング、誤った順序)の代わりにutf8_unicode_cs(大部分は大文字と小文字を区別する、厳密なマッチング、正しい順序付け)を使用することを強くお勧めします。

ユーザーの一致とは対照的に、フィールドの検索が意図されている場合は、utf8_general_ciまたはutf8_unicode_ciを使用してください。どちらも大文字と小文字は区別されず、1つが一致しなくなります(「β」は「S」に等しく、「ss」には等しくありません)。 utf8_german_ciのような言語固有のバージョンもあります。この場合、一致の喪失は指定された言語により適しています。

[編集 - ほぼ6年後]

MySQLでは "utf8"文字セットを推奨しません。代わりに "utf8mb4"文字セットを推奨します。それらはほぼ完全に一致しますが、もう少し(より多くの)Unicode文字を使用できます。

現実的には、MySQLは "utf8"仕様と一致するように "utf8"文字セットとそれぞれの照合順序を更新する必要がありましたが、代わりに、それらの不完全な "utf8"文字セットをすでに使用している人。

7
SEoF

私はこれらの照合表が役に立つことがわかりました。 http://collat​​ion-charts.org/mysql60/ 。私はどれが使用されているutf8_general_ciであるかわかりません。

例えば、これがutf8_swedish_ciのチャートです。どの文字が同じものとして解釈されるかを示します。 http://collat​​ion-charts.org/mysql60/mysql604.utf8_swedish_ci.html

4
jiv-e

データベースアップロードファイルで、行の前に次の行を追加します。

SET NAMES utf8;

そして、あなたの問題は解決されるべきです。

2
tapos ghosh