web-dev-qa-db-ja.com

MySQLは暗黙的にUTF文字をリテラル疑問符で置き換えます

this SO question に似た状況が発生しています。つまり、latin1テーブルにUTF8コンテンツが含まれているレガシーデータベースを使用しています(かなり醜いのは知っています)。

現在、完全にutf8で、データベースと連携する新しいアプリケーションから新しいデータを取得しています。他のレガシーシステムをサポートするために、アプリケーションはそのutf8データのコピーをレガシーテーブルに書き込みます。私が知る限り、utf8のものをlatin1テーブルに書き込んで、それらのデータを読み直してUTF8として表示できる限り、可能です。多くの tutorials この状況を長期にわたって修正する方法を説明していますが、絶対に必要でない限り、適用しない方がよいでしょう(レガシーシステムはすぐに却下されてしまいたくありません)可能であればこれを修正するためのダウンタイム)

これが私の問題を再現する最小限のSQLスクリプトです。

CREATE TABLE `articles` (
  `content` mediumtext NOT NULL,
  FULLTEXT KEY `content` (`content`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

SET NAMES utf8;
SET CHARACTER SET utf8;
-- Turkish Word for Croatia, second char is \xC4\xB1
INSERT INTO `articles` (`content`) VALUES ('Hırvatistan');

私のシステムでは、MySQLからエラーは発生しませんが、INSERTステートメントの後、Wordの2番目の文字が暗黙的に削除され、リテラル?( '\ x3F')に置き換えられます。

mysql> SELECT content, HEX(content), HEX('Hırvatistan') FROM articles;
+-------------+------------------------+--------------------------+
| content     | HEX(content)           | HEX('Hırvatistan')       |
+-------------+------------------------+--------------------------+
| H?rvatistan | 483F72766174697374616E | 48C4B172766174697374616E |
+-------------+------------------------+--------------------------+

ただし、同じスクリプトを http://sqlfiddle.com/ に貼り付けると、「ビルドスキーマ」を押すとエラーが発生します。

Incorrect string value: '\xC4\xB1rvat...' for column 'content' at row 1

なぜ私のシステムでは、無効なutf8文字が単に削除され、エラーが発生しないのですか?これを回避するために有効にするmysql設定値はありますか?

現在のlatin1(utf8コンテンツを含む)テーブル内に任意の種類の文字を許可する最も簡単な方法はどれですか?コンテンツがたくさんあるので、コンテンツをダンプして他の文字セットで再インポートするような解決策は避けたい

3
Fabio

私はこの問題を掘り下げるためにいくつかの試みをしました、これが結果です。

接続文字セット(つまり_SET NAMES utf8_)を設定すると、MySQLはエンコーディング変換を透過的に処理します。たとえば、UTF8接続を使用してlatin1テーブルにà (\xE0 in latin1 \xC3A0 in utf8)を挿入すると、UTF 8値が読み取られ、テーブルに_\xE0_として格納されます

_mysql> SELECT HEX('à');
+-----------+
| HEX('à')  |
+-----------+
| C3A0      |
+-----------+

mysql> INSERT INTO articles VALUES(50001, 'à');
Query OK, 1 row affected (0,00 sec)

mysql> SELECT content, HEX(content) FROM articles WHERE id_p = 50001;
+---------+--------------+
| content | HEX(content) |
+---------+--------------+
| à       | E0           |
+---------+--------------+
1 row in set (0,00 sec)
_

無効なutf8文字をlatin1に挿入すると、元の質問で示したように、それらが疑問符に置き換えられます。

私の問題を修正するために、元のテーブルでこのコマンドを実行する必要がありました(実際には、その小さなコピーで試しました)。文字セット、照合順序の変更を処理し、既存のデータも変換します。 latin1とutf8エンコーディングが異なるcharを使用してレコードを取得しました

_mysql> select HEX(BINARY SUBSTRING(content, 17, 1)), SUBSTRING(content, 17, 1) from articles where id_p = 40\G
*************************** 1. row ***************************
HEX(BINARY SUBSTRING(content, 17, 1)): 93
            SUBSTRING(content, 17, 1): “
1 row in set (0,00 sec)

mysql> ALTER TABLE `articles` CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
Query OK, 34905 rows affected (1 min 10,73 sec)
Records: 34905  Duplicates: 0  Warnings: 0

mysql> select HEX(BINARY SUBSTRING(content, 17, 3)), SUBSTRING(content, 17, 3) from articles where id_p = 40\G
*************************** 1. row ***************************
HEX(BINARY SUBSTRING(content, 17, 1)): E2809C
            SUBSTRING(content, 17, 1): “
1 row in set (0,00 sec)
_

変換後、__ charはコンテンツのutf8エンコーディングに置き換えられ、すべてのデータは引き続き読み取り可能です。また、latin1は1バイトあたり1バイト、1文字あたり3バイトまでのutf8を使用してデータの切り捨てを行わないため、変換によってcontent列タイプがMEDIUMTEXTからLONGTEXtに変更されました。

現在、変換されたテーブルに無効なutf8文字を挿入する実験を行っていますが、その結果は異なります。無効な(または サポートされていない4バイト )utf文字は、警告とともに保存された値から単に削除されるようです(警告が有効な場合にのみ表示されます)。

_$ mysql --show-warnings

mysql> INSERT INTO articles VALUES(90000, 0xC328);
Query OK, 1 row affected, 1 warning (0,00 sec)

Warning (Code 1366): Incorrect string value: '\xC3(' for column 'content' at row 1
mysql> SELECT 0xf09f8eb6;
+------------+
| 0xf09f8eb6 |
+------------+
| ????           |
+------------+
1 row in set (0,00 sec)

mysql> INSERT INTO articles VALUES(90001, 0xf09f8eb6);
Query OK, 1 row affected, 1 warning (0,00 sec)

Warning (Code 1366): Incorrect string value: '\xF0\x9F\x8E\xB6' for column 'content' at row 1
_

この後、元の例でも、有効にすると警告が表示されることがわかりました。

_-- With warnings enabled
mysql> INSERT INTO `articles` VALUES (50000, 'Hırvatistan');
Query OK, 1 row affected, 1 warning (0,00 sec)

Warning (Code 1366): Incorrect string value: '\xC4\xB1rvat...' for column 'content' at row 1
_

最後に、警告ではなくエラーをトリガーするには(データの損失を回避するため)、セッションのグローバルまたは(サーバーレベルで) SQLモード を変更します。

_mysql> SET SESSION sql_mode = 'TRADITIONAL';
Query OK, 0 rows affected (0,00 sec)

mysql> INSERT INTO `articles` VALUES (50000, 'Hırvatistan');
ERROR 1366 (HY000): Incorrect string value: '\xC4\xB1rvat...' for column 'content' at row 1
_
2
Fabio

最小限のテストケースをありがとう。

TEXT列はlatin1として宣言されています。 latin1には「ドットのないi」はないため、INSERTの実行中に、utf8の16進数C4B1から?に変換されました。

テーブル宣言を、できればutf8に変更します。
また削除してくださいSET CHARACTER SET utf8;-痛いようです!

「サイレント」変換について不満がある場合は、 http://bugs.mysql.com でバグを報告してください。

1
Rick James