mysql服务器设置了utf8,中文怎么还是乱码?

写在前面,本文不解释什么是字符集和各种字符集的区别,各位可以自行在互联网上自己寻找答案,本文只介绍数据库查询过程中涉及到的编码相关知识

首先:

我们通过下面的语句来引入我们今天的话题

show variables like 'character%';

1.png
上述我们看到mysql一部分属性的字符集(这里在安装时没有做任何调整),下面我们分别解释一下:

character_set_client   //客户端字符集                                             
character_set_connection //连接字符集                                                    
character_set_database //mysql数据库字符集
character_set_filesystem  //文件系统字符集                                                  
character_set_results   //结果集字符集                                                   
character_set_server    //服务器字符集                                                
character_set_system   //系统字符集   

当然mysql支持很多字符集,可以通过下面的语句来获取

2.png

通过上图可以看到,mysql支持的字符集达41种之多,这不是我们今天的重点,这里大家知道即可。但是我们要注意一下Default collation这个属性,它代表的是每种字符集的默认比较规则。

因为我们今天的重点在我们常用字符集utf8、utf8mb4上,所以我们看一下utf8、utf8mb4字符集的比较规则。下图如是:
3.png

从上图中,我们看到utf8(utf8mb4)支持很多比较规则,一般我们都是用默认(Default=Yes)的比较规则(utf8_general_ci和utf8mb4_general_ci)

当然这个可以修改,比如将utf8mb4_general_ci修改为utf8mb4_swedish_ci,意味着将utf8mb4的通用比较规则改为瑞士语比较规则

_ci代表不区分大小写的意思,其他的还有_ai(不区分重音)、_as(区分重音)、_cs(区分大小写)、_bin(以二进制方式做比较)

正题:

上述的讲解只是为了让下面的主题更加容易理解

mysql有4个粒度不同的字符集和比较规则:
1、服务器级别
2、数据库级别
3、表级别
4、属性级别

下面作者以本机mysql环境来演示(其他环境的mysql不代表和作者演示的属性值一致)
1、服务器级别:

4.png
5.png
通过命令我们看到我的mysql服务器的字符集是latin1、比较规则是latin1_swedish_ci

2、数据库级别
6.png
我们看到数据库的字符集是utf8mb4,其比较规则也是对应的utf8mb4_general_ci

数据库的字符集不能通过修改变量来改变它,它是通过创建数据库的时候指定的、或者没有指定直接使用的服务器级别的字符集和比较规则

3、表级别
和数据库类似,表级别的字符集可以在创建的时候指定、也可以不指定,直接使用数据库级别的字符集,如下图的2种建表语句

7.png

让我们来看一下,是不是像我们说的一样:
8.png

4、属性级别
属性级别的字符集,可以在创建列或者修改列的时候来指定,同一个道理,如果没有指定则直接使用所在表的字符集和对应的比较规则
现在我们讲kuya1表的name属性的字符集改为gbk

alter table kuya1 modify name varchar(32) character set gbk collate gbk_chinese_ci; 

再来看看kuya1表的字符集,可以看出表的字符集和列的不一样
9.png

编码解码过程:

在我们一条sql语句执行的过程,大概分为几个过程:
1、客户端发送请求
2、服务端接收请求
3、服务端处理请求
4、服务端生成响应
5、客户端接收响应

下面我们用实际例子来演示:
1、客户端发送请求
我们来启动一个客户端,并指定utf8字符集,意思是客户端将会用utf8字符集对请求的字符串进行编码
10.png

2、服务端接收请求
对服务端来讲,客户端发过来一串字符串,比如步骤1中的name=‘隔壁老王’,服务端怎么知道用什么编码集对‘隔壁老王’进行编码,然后进行下一步操作呢,是的就是character_set_client这个属性,你一定还记得文章开头的图
1.png
你现在知道了,服务端根据这个属性值来决定怎么编码,所以我们在表kuya中,能看到记录是正常的
12.png

那如果我们把这个属性修改为latin1呢
13.png
这是怎么回事?暴躁啊
原因是:你客户端是utf8,但是我偏偏用latin1来编码,最终展示的当然是乱码啦,就好比两个人说话,我得知你会中文,我用中文和你聊天,结果你丫的只会拉丁文,最后鸭同鸡讲。。。

3、服务端处理请求
为了验证后续操作,我们把character_set_client改回utf8

set character_set_client='utf8';

尽管服务端对请求的字符串序列按照character_set_client的指定的编码集进行编码,但是在真正处理的时候,又会将其按照character_set_connection编码集进行编码,步骤2中,可知character_set_connection=utf8,我们将其修改为latin1,进行验证一下
14.png

4、服务端生成响应

为了验证后续操作,我们把character_set_connection改回utf8

set character_set_connection='utf8';

通过上面得知,我们知道客户端对‘隔壁老王’这条记录是用utf8发送,服务端用utf8进行接收,并且用utf8进行处理,那查询出来的时候,一定能正常返回吗?

我们再做一下实验
15.png
大家看到了,‘隔壁老王’记录,返回的是????,隔壁老黄返回的是正常数据,为什么呢?好奇怪!

其实也不奇怪,返回数据是要按照属性character_set_results进行再次编码的,然后再返回给客户端的,所以‘隔壁老王’、'隔壁老刘'都不是按照latin1进行编码保存的,只有'隔壁老黄'是latin1字符集编码保存的,所以出现了上面的情况。

5、客户端接收响应
对步骤4来讲,‘隔壁老黄’能正常显示,最终还是因为character_set_results和客户端启动时default-character-set编码值一致,如果不一致,也会有问题,如果不指定default-character-set,取决于操作系统当前使用的字符集。

作者公众号:程序员阿牛
转载请注明出处
gzh