UTF-8字符的问题;我看到的不是我存储的内容

110

我尝试使用UTF-8遇到了麻烦。

我尝试了很多方法,以下是我的尝试结果:

  • 亚洲字符被替换成了????。即使对于欧洲文本,也会得到Se?or而不是Señor
  • 出现了奇怪的乱码(Mojibake),例如Señor新浪新闻代替新浪新闻
  • 出现了黑色菱形,如Se�or。
  • 最后,我陷入了一种情况,数据丢失了,或者至少被截断了:Se代替Señor
  • 即使我让文本看起来正确,它也不能正确排序

我错在哪里了?如何修复代码?如果可以,我可以恢复数据吗?

5个回答

194

这个问题困扰着该网站的参与者和许多其他人。

您列出了五种主要的字符集问题。

最佳实践

从现在开始,最好使用CHARACTER SET utf8mb4COLLATION utf8mb4_unicode_520_ci。(有一个新版本的Unicode排序正在进行中。)

utf8mb4utf8的超集,因为它处理需要表情符号和一些汉字的4字节utf8代码。

在MySQL之外,“UTF-8”指所有大小编码,因此有效地与MySQL的utf8mb4相同,而不是utf8

我将尝试使用这些拼写和大写来区分以下MySQL内部和外部。

您应该做什么的概述

  • 请将您的编辑器等设置为UTF-8。
  • HTML表单应该以如下方式开始:<form accept-charset="UTF-8">
  • 请确保您的字节已编码为UTF-8。
  • 在客户端建立使用UTF-8的编码。
  • 请声明列/表为CHARACTER SET utf8mb4(通过SHOW CREATE TABLE来检查)。
  • 在HTML开头添加<meta charset=UTF-8>
  • 存储过程会获取当前字符集/排序规则。它们可能需要重新构建。

始终使用UTF-8

计算机语言的更多详细信息(以及后续章节)

测试数据

使用工具或SELECT查看数据是不能完全信任的。太多这样的客户端,特别是浏览器,会尝试补偿不正确的编码,并在数据库被破坏时展示正确的文本。因此,请选择一个包含一些非英语文本的表和列,并执行以下操作:

SELECT col, HEX(col) FROM tbl WHERE ...

正确存储UTF-8的十六进制为:

  • 对于空格(任何语言):20
  • 对于英语:4x5x6x7x
  • 对于大多数西欧国家,重音字母应为Cxyy
  • 对于西里尔文、希伯来文和波斯/阿拉伯文:Dxyy
  • 对于大多数亚洲地区:Exyyzz
  • 对于表情符号和一些中文:F0yyzzww
  • 更多细节

问题的具体原因和解决方法

截断的文本(SeñorSe):

  • 要存储的字节未编码为utf8mb4。请修复此问题。
  • 同时,请检查读取期间的连接是否为UTF-8。

黑色菱形带问号(Se�or代表Señor); 存在以下情况之一:

情况1(原始字节不是UTF-8):

  • 要存储的字节未编码为utf8。请修复此问题。
  • 连接(或SET NAMES)用于INSERTSELECT的字符集不是utf8 / utf8mb4。 请修复此问题。
  • 还要检查数据库中的列是否为CHARACTER SET utf8(或utf8mb4)。

情况2(原始字节是UTF-8):

  • 连接(或SET NAMES)用于SELECT的字符集不是utf8 / utf8mb4。 请修复此问题。
  • 还要检查数据库中的列是否为CHARACTER SET utf8(或utf8mb4)。

只有当浏览器设置为<meta charset=UTF-8>时,才会出现黑色菱形。

问号(普通的,不是黑色菱形)(Se?or 表示 Señor):

  • 要存储的字节没有编码为 utf8/utf8mb4。请修复此问题。
  • 数据库中的列不是 CHARACTER SET utf8(或 utf8mb4)。请修复此问题。(使用 SHOW CREATE TABLE。)
  • 此外,请检查在读取期间的连接是否为 UTF-8。

乱码Señor 表示 Señor): (此讨论也适用于双重编码,其不一定可见。)

  • 需要存储的字节需要进行UTF-8编码。请修复此问题。
  • INSERTSELECT文本时的连接需要指定utf8或utf8mb4。请修复此问题。
  • 列需要声明为CHARACTER SET utf8(或utf8mb4)。请修复此问题。
  • HTML应以<meta charset=UTF-8>开头。

如果数据看起来正确,但无法正确排序,则可能是选择了错误的排序规则, 或者没有适合您需求的排序规则, 或者存在双重编码

可以通过执行上述SELECT .. HEX ..来确认双重编码

é should come back C3A9, but instead shows C383C2A9
The Emoji  should come back F09F91BD, but comes back C3B0C5B8E28098C2BD

也就是说,十六进制长度大约是应该长度的两倍。

这是由于从latin1(或其他编码)转换为utf8,然后将这些字节视为latin1并重复转换所导致的。

排序(和比较)不正确,因为例如按照字符串Señor排序。

尽可能修复数据

对于截断问号,数据丢失。

对于Mojibake / 双重编码,...

对于黑色菱形,...

在此列出了修复方法5种不同情况的5种不同修复方法;请谨慎选择

相关:字符集混合错误


如果客户端、数据库和表都是 utf8mb4,我似乎可以很好地存储表情符号。一些博客建议在 mysqld 中设置 collation-servercharacter-set-server。我真的需要更改 mysqld 吗?服务器设置有什么区别吗? - david_adler
1
@david_adler - 有多种方法可以实现这些设置的效果。最好的方法是使用客户端连接参数。其次是在连接后立即执行SET NAMES utf8mb4。毕竟,这是在客户端中声明编码方式。 - Rick James
有关配置Python、PHP和其他约40种编程语言的提示,请参见此链接:http://mysql.rjweb.org/doc.php/charcoll。 - Rick James
另外一点注意:如果涉及到FUNCTION或者STORED PROCEDURE,你可能在创建它时没有使用所需的字符集。请将其DROP掉,然后通过SET NAMES重新CREATE - Rick James
@dolmen - 是的,但有时这种情况发生在插入期间,并且会在表中留下错误数据。但是直到选择时才会注意到垃圾数据。在某些情况下,由于存储的只是“?”而无法选择正确的字符。 - Rick James
显示剩余5条评论

14

在服务器迁移后,我的两个项目也遇到了类似的问题。在经过大量搜索和尝试各种解决方案之后,我找到了这个:

mysqli_set_charset($con,"utf8mb4");

在我的配置文件中添加了这行后,一切正常!

当我想解决从HTML查询中插入数据时,我发现了这个适用于MySQLi的解决方案——PHP mysqli set_charset()函数


1
是的,这是导致字符集问题的几个原因之一。请注意:该语法仅适用于PHP,而非其他应用程序语言,并且仅在使用mysqli而非PDO时有效。 - Rick James

5
我也在寻找同样的问题。花了我将近一个月的时间才找到合适的解决方案。
首先,您需要更新数据库中所有最新的字符和排序规则为utf8mb4,或者至少支持UTF-8数据的字符集和排序规则。
对于Java:
在建立JDBC连接时,在连接URL中添加useUnicode=yes&characterEncoding=UTF-8作为参数,就可以正常工作。
对于Python:
在查询数据库之前,尝试在游标上强制执行这个设置。
cursor.execute("SET NAMES utf8mb4")
cursor.execute("SET CHARACTER SET utf8mb4")
cursor.execute("SET character_set_connection=utf8mb4")

如果它不起作用,祝你找到正确的解决方案。

一个月?那真是太快了。我花了一年多的时间来制定这个问答。Java看起来不错。SETs不是Python的“正确”方式;请参见http://mysql.rjweb.org/doc.php/charcoll#python。许多其他语言在该博客的其他地方都有讨论。 - Rick James
1
@RickJames,但是这个问题存在于Mysql-Python 1.2.4以下的版本中,所以“SET”语句基本上是一个解决方法。 - Ashish Bhatt
1
"cursor.execute" 附近的内容应该如何格式化?每个都分开一行吗?还是其他什么?"*" 是字面意思还是表示斜体格式? - Peter Mortensen

2
  1. Set your code IDE language to UTF-8

  2. Add <meta charset="utf-8"> to your webpage header where you collect data form.

  3. Check your MySQL table definition looks like this:

     CREATE TABLE your_table (
       ...
     ) ENGINE=InnoDB DEFAULT CHARSET=utf8
    
  4. If you are using PDO, make sure

    $options = array(PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES utf8');
    $dbL = new PDO($pdo, $user, $pass, $options);
    
如果您已经拥有一个存在上述问题的大型数据库,您可以尝试使用SIDU以正确的字符集导出,并使用UTF-8导入回来。

2
PDO最好使用charset选项完成:$db = new PDO('dblib:host=host;dbname=db;charset=UTF8', $user, $pwd);(这在我的“charcoll”文档链接中列出)。 - Rick James
你比我专业20K :) 是的,你可以为列设置字符集。尽量不要过度使用它,否则会增加管理时间。同样地,你可以授予对MySQL表的某个列的访问权限。但是,除非你没有更好的选择,否则不必使用它。 - SIDU
如果我将数据库更改为utf8,是否需要重新启动数据库以应用更改?并且是否存在数据丢失的可能性? - pramodpxi
2
@ppmakeitcount:不需要重启MySQL即可使ALTER DATABASE语句生效。但是,更改数据库的默认字符集不会影响当前数据库中的任何表;它只对新表(例如CREATE TABLE)产生影响,这些表没有为表指定默认字符集时,才会使用数据库默认字符集。(同样地,更改表的默认字符集不会影响已经存在于表中的列;它只对添加到表中的列产生影响,当没有指定列字符集时。 - spencer7593
1
@bballdave025 - 谢谢。我花了很长时间——首先是发现所有不同的情况,然后找出每种情况的原因,再花更多时间简洁地解释它们。 - Rick James
显示剩余6条评论

-5

根据服务器的设置,您必须相应地更改编码。从您所说的来看,utf8 应该是最好的选择。但是,如果您遇到奇怪的字符,将网页编码更改为 ANSI 可能会有所帮助。

当我设置 PHP MySQLi 时,这对我很有帮助。这可能会帮助您更好地理解:在 Notepad++ 中将 ANSI 转换为 UTF-8


1
Notepad 的 “ANSI” 可能最接近 MySQL 的 “latin1”。该链接中的 0x93 是“ ”,可能来自 Word 等地方。你可以将其转换为 utf8(十六进制 E2809C),或者告诉 MySQL 数据是 “latin1”,希望你不会在其他地方出现问题。 - Rick James

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接