系统环境:
操作系统:MAC OS 10.11.6
MySQL:Server version: 5.6.21 MySQL Community Server (GPL)
Navicat for MySQL: version 9.3.1 - standard
1、问题发现
在客户端执行用户注册,用户名支持 emoji 表情,注册完成后,在APP客户端以及网页前端都可以正常显示带 emoji 表情的用户昵称,说明数据库的数据存储是正常的,为什么在 Navicat for mysql 客户端里打开用户表,显示用户昵称是乱码呢?
mysql> select nick_name from tb_user where id = 91;
+---------------+
| nick_name |
+---------------+
| 涛声依旧? |
+---------------+
1 rows in set (0.03 sec)
如上,查询结果的的用户昵称【涛声依旧?】,出现了一个问号,这里本应该是一个 emoji 表情符号。
2、问题分析
MySQL 采用 C/S 架构,所以它有服务端和客户端(如:MySQL自带的客户端、Navicat for MySQL等), 如果两端分布在不同的的主机上,那么两端通常需要通过 TCP/IP 获其他协议建立连接,然后实现通信或数据传输。跟 HTTP 协议有点异曲同工,HTTP 发起请求时会在 Header 里附上客户端“信息体”采用的字符集,同理,MySQL 两端也需要提前沟通好通信采用的字符集,否则就是鸡同鸭讲,乱码就会出现了。
MySQL提供了 character_set_client、character_set_connection 和 character_set_results 三个字符集变量来辅助客户端与服务端的通信。
- character_set_client:服务器会将请求(如:一条 SQL 查询语句)的字节序列当作采用 character_set_client 字符集进行编码的字节序列。
- character_set_connection:连接数据库时的字符集。
- character_set_results:数据库给客户端返回时使用的字符集。
一般显示出现乱码,通常和字符集变量设置有关,我们知道字符集与数据库存储数据有关系,但客户端(Navicat)和服务端之间的交互也受到字符集影响。上面提过,APP客户端以及网页前端都正常显示,说明数据存储是没问题的,问题很可能出在两端的交互上,也就说跟上面三个变量有关系。
现在我们在 Navicat 客户端打开 console 窗口查询下这三个变量的值:
show variables like "character_set%";
结果:
可以看到,这三个变量的字符集都是 utf8,,mysql 里的 utf8 字符集并不是真正的 utf8, 而是阉割版的,最长只有3个字节。utf8 字符集显示简体中文没有问题,但是显示 4 个字节的 emoji 表情就不够用了。
character_set_results = utf8, 表示查询结果会按这个字符集进行编码。查询的字段(用户昵称)包括了emoji 表情符,被转成了 utf8 字符集,所以就变成了乱码。
既然发现问题,就好办了,我们把这三个变量(其实只需要设置 character_set_results )设置为可以兼容 emoji 表情的的字符集 utf8mb4 即可。
# 等效于将三个变量同时设置为utf8mb4字符集
set names utf8mb4
修改后再次查询,可以看到,这个 emoji 表情已经可以正常显示了。
3、问题解决
上面提到的三个字符集变量,属于一个 Session 作用域,本次客户端与服务器的连接关闭后就会失效,重新打开一个新的连接,仍然会恢复到之前的设置。
回到最早的问题,怎么能让 Navicat 打开数据表始终可以看到正常的 emoji 表情呢?其实 Navicat for Mysql 可以设置数据库的「连接属性」,里面可以设置「客户端字符集」,但是笔者的 Navicat 估计是版本比较旧的版本,暂时没有找到对应的修改项,不过上面的原因已经分析清楚了,读者可以自行解决了。
4、结语
以上出现了一些关于字符集和字符序的术语,其实 MySQL 的一些莫名其妙的错误包括“乱码”都和它们密切相关。 所以有必要对它们有清晰的了解,如果感兴趣,可以参考笔者另外一篇文章:
MySQL 字符集概念、原理及配置之图文详解