web-dev-qa-db-ja.com

MySQL照合の違法な組み合わせ

私の製品ログを見た後、私は言及するいくつかのエラーがあります:

[2012-08-31 15:56:43] request.CRITICAL: Doctrine\DBAL\DBALException: 
An exception occurred while executing 'SELECT t0.username ....... FROM fos_user t0 WHERE t0.username = ?'
with params {"1":"Nrv\u29e7Kasi"}:

SQLSTATE[HY000]: General error: 1267 Illegal mix of collations (latin1_swedish_ci,IMPLICIT)
and (utf8_general_ci,COERCIBLE) for operation '=' 

ただし、doctrine cfg:

doctrine:
    dbal:
        charset:  UTF8

私のMySQLテーブルはすべてlatin1_swedish_ciにあるようですので、私の質問は次のとおりです。

複雑な問題や注意事項なしに、すべてのテーブルの照合順序を手動でutf8_general_ciに変更できますか?

18
sf_tristanb

次の定義を理解しておくと役立ちます。

  • 文字エンコードは、各シンボルがバイナリでどのように表されるか(したがって、コンピューターに格納されるか)を詳しく説明します。たとえば、記号é(U + 00E9、アキュート付きラテン小文字E)は encoded as 0xc3a9 in TF-8 (which MySQLは Windows-1252 (MySQLはutf8と呼びます)で0xe9)とlatin1を呼び出します。

  • 文字セットは、特定の文字エンコードを使用して表すことができる記号のアルファベットです。紛らわしいことに、この用語は文字エンコードと同じ意味で使用されます。

  • collat​​ionは文字セットの順序であるため、文字列を比較できます。例:MySQLの latin1_swedish_ci 照合は、文字の最もアクセントのあるバリエーションを基本文字と同等として扱いますが、その latin1_general_ci 照合は前にそれらを順序付けます次の基本文字ですが、同等ではありません(åäößなどの文字の順序など、他のより重要な違いもあります)。

MySQLは、 式の照合 で説明されているように、特定の式に適用する照合を決定します。特に、列の照合は文字列リテラルの照合よりも優先されます。

クエリのWHERE句は、次の文字列を比較します。

  1. 列の文字セット(Windows-1252)でエンコードされ、照合順序fos_user.username(強制値2)の設定を表すlatin1_swedish_ciの値。と

  2. 文字列リテラル'Nrv⧧Kasi'は、接続の文字セット(UTF-8、Doctrineで構成)でエンコードされ、接続の照合順序utf8_general_ci(強制値4)の設定を表します。

これらの文字列の最初の文字列は2番目の文字列よりも強制力の値が低いため、MySQLはその文字列の照合順序latin1_swedish_ciを使用して比較を実行しようとします。これを行うために、MySQLは2番目の文字列をlatin1に変換しようとしますが、文字がその文字セットに存在しないため、比較は失敗します。


警告

列が現在どのようにエンコードされているかを検討するために、少し一時停止する必要があります。fos_user.username次の文字を含む文字列と等しいレコードをフィルタリングしようとしていますcannotその列に存在しません

doesにそのような文字が含まれていると思われる場合は、接続文字エンコードがMySQLに受信バイトを解釈させる何か(例:latin1)に設定されているときに、おそらく列に書き込みました。すべてWindows-1252文字セットに含まれる文字としてのシーケンス。

この場合、先に進む前に、データを修正する必要があります。

  1. 現在のエンコーディングと異なる場合は、そのような列をデータ挿入で使用された文字エンコーディングに変換します。

    ALTER TABLE fos_users MODIFY username VARCHAR(123) CHARACTER SET foo;
    
  2. そのような列に関連付けられているエンコード情報をbinary文字セットに変換して削除します。

    ALTER TABLE fos_users MODIFY username VARCHAR(123) CHARACTER SET binary;
    
  3. それらを関連する文字セットに変換することにより、データが実際に送信されたエンコーディングをそのような列に関連付けます。

    ALTER TABLE fos_users MODIFY username VARCHAR(123) CHARACTER SET bar;
    

マルチバイトエンコーディングから変換する場合、変換された文字列の可能な最大長に対応するために、列のサイズを大きくする(またはタイプを変更する)必要がある場合があることに注意してください。


列が正しくエンコードされていることを確認したら、次のいずれかによってUnicode照合を使用して比較を強制することができます。

  • fos_user.usernameをUnicode文字セットに明示的に変換します。

    WHERE CONVERT(fos_user.username USING utf8) = ?
    
  • 文字列リテラルの強制力の値を列よりも低くするように強制します(列の値がUTF-8に暗黙的に変換されます)。

    WHERE fos_user.username = ? COLLATE utf8_general_ci
    

または、あなたが言うように、列をUnicodeエンコーディングに永続的に変換し、その照合順序を適切に設定することもできます。

複雑な問題や予防策を講じることなく、すべてのテーブルの照合順序を手動でutf8_general_ciに変更できますか?

主な考慮事項は、Unicodeエンコーディングはシングルバイト文字セットよりも多くのスペースを占めるため、次のようになります。

  • より多くのストレージが必要になる場合があります。

  • 比較は遅くなる可能性があります。そして

  • インデックスプレフィックスの長さを調整する必要がある場合があります(最大値はバイト単位であるため、以前よりも少ない文字を表す場合があることに注意してください)。

また、 ALTER TABLE Syntax に記載されているように、次の点にも注意してください。

テーブルのデフォルトの文字セットとすべての文字列を変更するには( CHARVARCHARTEXT )新しい文字セットにするには、次のようなステートメントを使用します。

ALTER TABLEtbl_name文字セットに変換charset_name;

VARCHAR または TEXT タイプのいずれかのデータ型を持つ列の場合、CONVERT TO CHARACTER SETはデータを変更します必要に応じて入力して、新しい列が元の列と同じ数の文字を格納するのに十分な長さになるようにします。たとえば、 TEXT 列には2つの長さのバイトがあり、最大65,535までの値のバイト長を列に格納します。 latin1TEXT 列の場合、各文字には1バイトが必要であるため、列には最大65,535文字を格納できます。列がutf8に変換される場合、各文字は最大3バイトを必要とする場合があり、最大可能長は3×65,535 = 196,605バイトです。その長さは TEXT 列の長さバイトに収まらないため、MySQLはデータ型を MEDIUMTEXT に変換します。これは最小です。長さバイトが196,605の値を記録できる文字列型。同様に、 VARCHAR 列は MEDIUMTEXT に変換される可能性があります。

今説明したタイプのデータ型の変更を避けるために、CONVERT TO CHARACTER SETを使用しないでください。代わりに、MODIFYを使用して個々の列を変更してください。

60
eggyal

そのとおり。私はこの問題に遭遇しました、そして最良の迅速で速い解決策は

         CONVERT(fos_user.username USING utf8)
8
JGutierrezC

次のようにコマンドでテーブルの文字セットを変換するだけです。

ALTER TABLE tbl_name CONVERT TO CHARACTER SET utf8;
3
Marvin W