在计算机的眼中只有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点:数据库
固然,这个规则不是我要了解的重点,只须要知道unicode定义了字符的编码,UTF-8是一种实现Unicode的方式就好。bash
看一下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)
复制代码
根据上面的相关variable,先看看和建立对象相关的变量。 character_set_server,这个变量为建立DB时候的默认值,Mysql在建立对象的时候有一个阶梯状的规则。若是建立database的时候没有指定这个db的字符集,就使用character_set_server的值;若是在建立表的时候没有指定字符集就使用这个表所在db的字符集,若是建立列的时候没有指定字符集,就使用这个表的字符字符集。测试
这个是主要的参数,这部分的参数主要有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范围内的,就不转换,其它状况就转。
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)插入了一条数据,发现目前显示都正确。
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的编码去理解,理解错了天然最终的存储也就错了。
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编码存储起来,这个过程无非就是作了一些无谓的转换,可是结果是正常的。
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解析,实际上就是一个逆向过程,相似负负得正~