Mysql中的字符集

一、字符集基础

在计算机的眼中只有0和1,可是在人类世界中却有上百种语言,每种语言又有成千上万的文字,那么如何在计算中表示人类世界中的这些文字呢?html

在上个世纪60年代的时候,美国首先定义了一套规则,在这个规则中一共定义了128个字符对应的二进制编码,好比大写的字母A是65(01000001),空格是32(00100000)等等。经过这个规则,计算机也就知道了01000001表示字母A,这也就是所谓的ASCII。在计算机中使用一个字节来存储ASCII。mysql

很显然,只有ASCII是不够的,128个字符是远远不能表示各类语言的,这个时候各个地区也就逐渐定义了本身的编码方式,好比在欧洲的一些国家就经过启用ASCII编码的最高位来拓展支持的字符,由于在ASCII中只有128个字符,一个字节有8位,那么在ASCII中的最高位确定是0,经过启动这个闲置的最高位就把支持的字符扩展到256个了,已经彻底能知足本身地区的需求。可是,随着互联网的发展,地理上隔开的世界被一根根的网线链接在一块儿了,若是各个国家地区都使用本身的编码方式,显然是有问题的。因而就有了unicode,我的感受Unicode的做用就像他的名字同样unique code,几乎给世界上的每个符号都作了定义,若是你们都使用这个定义去作,那就不存在相互看不懂编码的问题,好比:linux

到此为止,已经解决了编码的问题了,可是又有了新的问题。unicode只是定义了一个字符怎么去编码,没有说明怎么去存储啊。好比“龟”在unique的代码为9F9F(1001111110011111)他须要2个字节,有的符号对应的编码更大可能须要3个字节或者更多。如何区分呢?好比0001111110011111怎么知道究竟是表示两个ACSII字符仍是表示一个占用2个字节的Unicod字符呢?sql

上面的这个问题就须要交给UTF-8(或者其余)去解决了,一句话就是Unicode定义了字符的编码,UTF-8定义了这个编码的实现(存储)方式,在Unicode的编码中,最特别的一点就是他是变长的,变长的好处就是按需求占用空间,试想一下,用3个字节去存储一个单字符划算吗?在UTF-8的实现规则中只有2点:数据库

  • 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。所以对于英语字母,UTF-8 编码和 ASCII 码是相同的。
  • 对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一概设为10。剩下的没有说起的二进制位,所有为这个符号的 Unicode 码。

固然,这个规则不是我要了解的重点,只须要知道unicode定义了字符的编码,UTF-8是一种实现Unicode的方式就好。bash

二、Mysql中的字符集参数

看一下Mysql中和字符集相关的参数服务器

mh01@3306>show variables like '%char%';
+--------------------------+---------------------------------------------------------------+
| Variable_name            | Value                                                         |
+--------------------------+---------------------------------------------------------------+
| character_set_client     | utf8                                                          |
| character_set_connection | utf8                                                          |
| character_set_database   | utf8                                                          |
| character_set_filesystem | binary                                                        |
| character_set_results    | utf8                                                          |
| character_set_server     | utf8                                                          |
| character_set_system     | utf8                                                          |
| character_sets_dir       | /usr/local/mysql-5.7.16-linux-glibc2.5-x86_64/share/charsets/ |
+--------------------------+---------------------------------------------------------------+
8 rows in set (0.00 sec)
复制代码

2.1建立对象相关

根据上面的相关variable,先看看和建立对象相关的变量。 character_set_server,这个变量为建立DB时候的默认值,Mysql在建立对象的时候有一个阶梯状的规则。若是建立database的时候没有指定这个db的字符集,就使用character_set_server的值;若是在建立表的时候没有指定字符集就使用这个表所在db的字符集,若是建立列的时候没有指定字符集,就使用这个表的字符字符集。测试

2.2服务器和客户端通讯相关

这个是主要的参数,这部分的参数主要有character_set_client、character_set_connection、character_set_results这几个ui

首先看一下character_set_client,这个定义了客户端传输的字符集,用来告诉Mysql Server,客户端使用character_set_client的值来传输数据。也就是说Mysql老是认为客户端传输的是用该参数对应的编码来写的,可是真实状况却不必定,若是这个参数设置错误,可能会致使乱码,详情能够看后续的测试部分。编码

character_set_connection,Mysql须要把字符集转换为character_set_connection来处理SQL语句

charcter_set_results,这个参数定义了用何种字符集返回给客户端。 下图是Mysql在处理客户端和服务器通讯时候字符集的转换方式:

转换规则:若是 character_set_client 和 character_set_connection 同样,或者当前的字符编码是和ASCII兼容,而且都是ASCII范围内的,就不转换,其它状况就转。

2.3其它

character_set_system是一个只读变量,说明元数据的编码方式 character_set_database表示了当前数据库的默认字符集,好比在使用use切换数据库的时候,该参数会随着当前数据库默认字符集的改变而改变。

三、修改他们会有什么影响

在通常实践中,常常会使用set names UTF8的方式去设置相关的字符集参数,这种方式通常是同时修改了三个变量分别是

SET character_set_client = UTF8;
SET character_set_results = UTF8;
SET character_set_connection = UTF8;
复制代码

为何要同时这个这三个参数,若是这个三个参数不一致会发生什么状况。 先建立一个表,用UTF8的方式插入一些数据

CREATE TABLE `char_test` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8

set names utf8;

mh01@3306>insert into char_test (1,'哈哈');
mh01@3306>select id,name,hex(name) from char_test;
+------+--------+--------------+
| id   | name   | hex(name)    |
+------+--------+--------------+
|    1 | 哈哈   | E59388E59388 |
+------+--------+--------------+
1 row in set (0.00 sec)
复制代码

使用UTF8的终端(也就意味中客户端的字符集都是UTF8)插入了一条数据,发现目前显示都正确。

3.1修改character_set_client的值

mh01@3306>set character_set_client=gbk;
Query OK, 0 rows affected (0.00 sec)
mh01@3306>insert into char_test values (2,'哈哈'); 
Query OK, 1 row affected (0.01 sec)
mh01@3306>select id,name,hex(name) from char_test;
+------+-----------+--------------------+
| id   | name      | hex(name)          |
+------+-----------+--------------------+
|    1 | 哈哈      | E59388E59388       |
|    2 | 鍝堝搱    | E98D9DE5A09DE690B1 |
+------+-----------+--------------------+
2 rows in set (0.00 sec)

复制代码

从测试中能够看出,如今已经出现乱码了。为何?

客户端使用的是UTF8,那么在发送“哈哈”的时候就会使用“E59388E59388”的编码去发送,这里的“哈哈”占用了6个字节。

当Mysql Server接收到这个字符的时候,根据character_set_client的值,Mysql认为客户端是用GBK作编码的,这里就出现问题了由于实际上客户端使用的是UTF8的编码。根据上面提到的转换规则(character_set_client的值和character_set_connection的值不同)就会发生字符集的转换。由于Mysql认为这个编码是GBK的编码,因此就认为“E59388E59388”是三个字符(对应的汉子就是“鍝堝搱”),而后把“鍝堝搱”三个字符转换为UTF8,因此最终会发现id=2的值竟然多了3字节。由于Mysql从一开始就是把UTF8的编码当成了GBK的编码去理解,理解错了天然最终的存储也就错了。

3.2修改character_set_connection

mh01@3306>set names utf8;
Query OK, 0 rows affected (0.00 sec)
mh01@3306>set character_set_connection=gbk;
Query OK, 0 rows affected (0.00 sec)
mh01@3306>insert into char_test values (3,'哈哈'); 
Query OK, 1 row affected (0.01 sec)
mh01@3306>select id,name,hex(name) from char_test;
+------+-----------+--------------------+
| id   | name      | hex(name)          |
+------+-----------+--------------------+
|    1 | 哈哈      | E59388E59388       |
|    2 | 鍝堝搱    | E98D9DE5A09DE690B1 |
|    3 | 哈哈      | E59388E59388       |
+------+-----------+--------------------+
3 rows in set (0.00 sec)

复制代码

正常显示,为何?客户端的编码是UTF8,Mysql Server收到后根据character_set_client的设置认为是UTF8编码(答对了),可是由于character_set_connection的编码是GBK,须要进行转换。和第一个例子不同的地方是,在这里Mysql的理解是正确的(上一个例子中Mysql是误把“哈哈”理解为“鍝堝搱”),因此Mysql会正确的把UTF8编码转换为GBK编码。最后,因为表的编码是UTF8,因此Mysql又把GBK转换为UTF8编码存储起来,这个过程无非就是作了一些无谓的转换,可是结果是正常的。

3.3只修改character_set_results

mh01@3306>set character_set_results=gbk;                       
Query OK, 0 rows affected (0.00 sec)

mh01@3306>insert into char_test values (4,'哈哈'); 
Query OK, 1 row affected (0.01 sec)

mh01@3306>select id,name,hex(name) from char_test;
+------+--------+--------------------+
| id   | name   | hex(name)          |
+------+--------+--------------------+
|    1 |        | E59388E59388       |
|    2 | 哈哈   | E98D9DE5A09DE690B1 |
|    3 |        | E59388E59388       |
|    4 |        | E59388E59388       |
+------+--------+--------------------+
4 rows in set (0.00 sec)

复制代码

由于character_set_connection、character_set_client、客户端、表都是使用UTF8编码,因此在存储的时候存储的是正确的编码,“哈哈”=E59388E59388。由于character_set_results是GBK,因此作了一次转换,注意此次转换也是正常的转换,问题出在客户端这里,Mysql使用GBK编码把结果返回给客户端,可是客户端的使用的UTF8的,客户端没那么聪明,他会认为本身收到的都是UTF8的编码,可是实际上他收到的编码是GBK的编码,因此这回是客户端理解错误了,也就是显示出了错误的结果。

有意思的是,为何id=2的显示正常?由于在id=2的插入过程当中,使用了UTF8-->错误理解为GBK编码--->转换为UTF8的方式,而返回的时候,由于server会将存的UTF8又给转回GBK,而后客户端又拿着这个GBK误觉得是UTF8解析,实际上就是一个逆向过程,相似负负得正~

4 参考

字符编码笔记:ASCII,Unicode 和 UTF-8

MySQL · 答疑解惑 · set names 都作了什么

相关文章
相关标签/搜索