MySQL 默默地将 UTF 字符替换为文字问号

Fab*_*bio 6 mysql character-set utf-8 encoding

我遇到了类似于这个问题的情况,即我正在使用一个旧数据库,该数据库在 latin1 表中包含 UTF8 内容(我知道非常难看)。

\n\n

现在我正在从一个完全 utf8 的新应用程序获取新数据,并与其数据库一起使用。为了支持其他遗留系统,应用程序还在遗留表中写入其 utf8 数据的副本。据我所知,只要您读回并将这些数据显示为 UTF8,就应该可以在 latin1 表中写入 utf8 内容。有很多教程解释了如何长期解决这种情况,但我宁愿不应用它们,除非绝对必要(遗留系统将很快被解雇,我不希望有停机时间来解决这个问题,如果可能的)

\n\n

这是一个最小的 SQL 脚本,它重现了我的问题:

\n\n
CREATE TABLE `articles` (\n  `content` mediumtext NOT NULL,\n  FULLTEXT KEY `content` (`content`)\n) ENGINE=MyISAM DEFAULT CHARSET=latin1;\n\nSET NAMES utf8;\nSET CHARACTER SET utf8;\n-- Turkish word for Croatia, second char is \\xC4\\xB1\nINSERT INTO `articles` (`content`) VALUES (\'H\xc4\xb1rvatistan\');\n
Run Code Online (Sandbox Code Playgroud)\n\n

在我的系统中,我没有从 MySQL 收到错误,但在INSERT语句之后,该单词的第二个字符被默默删除并替换为文字?(\'\\x3F\')。

\n\n
mysql> SELECT content, HEX(content), HEX(\'H\xc4\xb1rvatistan\') FROM articles;\n+-------------+------------------------+--------------------------+\n| content     | HEX(content)           | HEX(\'H\xc4\xb1rvatistan\')       |\n+-------------+------------------------+--------------------------+\n| H?rvatistan | 483F72766174697374616E | 48C4B172766174697374616E |\n+-------------+------------------------+--------------------------+\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是,如果我将相同的脚本粘贴到http://sqlfiddle.com/上,当我按“构建架构”时会收到错误消息,其中指出:

\n\n
Incorrect string value: \'\\xC4\\xB1rvat...\' for column \'content\' at row 1\n
Run Code Online (Sandbox Code Playgroud)\n\n

为什么在我的系统上,无效的 utf8 字符被简单地删除而我没有收到任何错误?是否有任何 mysql 配置值可以启用以避免这种情况?

\n\n

在我当前的 latin1 (带有 utf8 内容)表中允许任何类型的 char 的最简单方法是什么?我有很多内容,我希望避免转储内容并使用其他字符集重新导入等解决方案

\n

Fab*_*bio 4

我做了一些尝试来深入研究这个问题,结果如下。

\n\n

当您设置连接字符集(即SET NAMES utf8)时,MySQL 会透明地为您处理编码转换。例如,如果我\xc3\xa0 (\\xE0 in latin1 \\xC3A0 in utf8)使用 UTF8 连接在 latin1 表中插入 a ,它会读取 UTF 8 值并将其存储在表中\\xE0

\n\n
mysql> SELECT HEX(\'\xc3\xa0\');\n+-----------+\n| HEX(\'\xc3\xa0\')  |\n+-----------+\n| C3A0      |\n+-----------+\n\nmysql> INSERT INTO articles VALUES(50001, \'\xc3\xa0\');\nQuery OK, 1 row affected (0,00 sec)\n\nmysql> SELECT content, HEX(content) FROM articles WHERE id_p = 50001;\n+---------+--------------+\n| content | HEX(content) |\n+---------+--------------+\n| \xc3\xa0       | E0           |\n+---------+--------------+\n1 row in set (0,00 sec)\n
Run Code Online (Sandbox Code Playgroud)\n\n

当我将无效的 utf8 字符插入到 latin1 中时,它会将它们替换为问号,如我在原始问题中所示。

\n\n

为了解决我的问题,我必须在原始表上运行此命令(实际上我在它的一个小副本上尝试过)。它负责更改字符集、排序规则并转换现有数据。我用一个字符进行记录,该字符的 latin1 和 utf8 编码不同

\n\n
mysql> select HEX(BINARY SUBSTRING(content, 17, 1)), SUBSTRING(content, 17, 1) from articles where id_p = 40\\G\n*************************** 1. row ***************************\nHEX(BINARY SUBSTRING(content, 17, 1)): 93\n            SUBSTRING(content, 17, 1): \xe2\x80\x9c\n1 row in set (0,00 sec)\n\nmysql> ALTER TABLE `articles` CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;\nQuery OK, 34905 rows affected (1 min 10,73 sec)\nRecords: 34905  Duplicates: 0  Warnings: 0\n\nmysql> select HEX(BINARY SUBSTRING(content, 17, 3)), SUBSTRING(content, 17, 3) from articles where id_p = 40\\G\n*************************** 1. row ***************************\nHEX(BINARY SUBSTRING(content, 17, 1)): E2809C\n            SUBSTRING(content, 17, 1): \xe2\x80\x9c\n1 row in set (0,00 sec)\n
Run Code Online (Sandbox Code Playgroud)\n\n

转换后,\xe2\x80\x9c内容中的 char 被替换为 utf8 编码,所有数据仍然可读。转换还将content列类型从更改MEDIUMTEXTLONGTEXt,因为 latin1 每个字符使用 1 个字节,而 utf8 每个字符最多使用 3 个字节以避免数据截断。

\n\n

现在我正在尝试将无效的 utf8 字符插入到转换后的表中,并且得到了不同的结果。似乎无效(或不支持 4 字节)utf 字符只是从存储值中删除并带有警告(仅在启用警告时显示)

\n\n
$ mysql --show-warnings\n\nmysql> INSERT INTO articles VALUES(90000, 0xC328);\nQuery OK, 1 row affected, 1 warning (0,00 sec)\n\nWarning (Code 1366): Incorrect string value: \'\\xC3(\' for column \'content\' at row 1\nmysql> SELECT 0xf09f8eb6;\n+------------+\n| 0xf09f8eb6 |\n+------------+\n|            |\n+------------+\n1 row in set (0,00 sec)\n\nmysql> INSERT INTO articles VALUES(90001, 0xf09f8eb6);\nQuery OK, 1 row affected, 1 warning (0,00 sec)\n\nWarning (Code 1366): Incorrect string value: \'\\xF0\\x9F\\x8E\\xB6\' for column \'content\' at row 1\n
Run Code Online (Sandbox Code Playgroud)\n\n

之后,我发现在我的原始示例中,如果启用它们,也会显示警告:

\n\n
-- With warnings enabled\nmysql> INSERT INTO `articles` VALUES (50000, \'H\xc4\xb1rvatistan\');\nQuery OK, 1 row affected, 1 warning (0,00 sec)\n\nWarning (Code 1366): Incorrect string value: \'\\xC4\\xB1rvat...\' for column \'content\' at row 1\n
Run Code Online (Sandbox Code Playgroud)\n\n

最后,要触发错误而不仅仅是警告(以避免数据丢失),只需更改会话或全局(在服务器级别)的SQL 模式

\n\n
mysql> SET SESSION sql_mode = \'TRADITIONAL\';\nQuery OK, 0 rows affected (0,00 sec)\n\nmysql> INSERT INTO `articles` VALUES (50000, \'H\xc4\xb1rvatistan\');\nERROR 1366 (HY000): Incorrect string value: \'\\xC4\\xB1rvat...\' for column \'content\' at row 1\n
Run Code Online (Sandbox Code Playgroud)\n