编码:字符 -> 二进制;
解码:二进制 -> 字符;
为什么会出现乱码?因为编码和解码的规则不同。本质上都是同样的一串二进制流,按照不同的规则解读的结果当然是不同的。类比一下我们的时间戳转时间的场景,时间戳就好比是二进制,时区就好比是不同的字符集,同一个时间戳用不同的时区转换,得到的结果当然是不同的。所以,我们只要保证编码和解码用同一套字符集就不会出现乱码了。
mysql里支持的字符集
这里主要指存储数据的字符集。可以使用如下命令查看:
show charset
另外我们可以设置不同级别的字符集,服务器、数据库、表、列四个级别。
存储不同字符集的数据,消耗的空间是不同的。比如ascii字符集,就是固定一个字节。但是对于utf8字符集,需要1-3个字节,这是变长字符集,所以空间是不固定的。
mysql中utf8其实指的是utf8mb3字符集,是mysql里的一种原始utf8字符集的变种,原始的utf8字符集需要1-4字节,而utf8mb3只需要1-3字节。原始的utf8字符集在mysql里叫utf8mb4。
字符集转换
在客户端发起一条sql语句,到接受服务端返回,这个过程中的字符集是如何转换的?
1.客户端使用mysql client发起sql语句查询,此时在客户端机器上,使用操作系统的字符集对命令进行了编码,转成了二进制流发送到网络中;
2.mysql服务端接受二进制流,使用character_set_client指定的字符集解码二进制流;
3.将字符流使用character_set_connection指定的字符集转换;
4.将字符集与列数据作比较;
5.将结果使用character_set_results指定的字符集编码,返回给客户端;
这里有个问题,为什么需要转成character_set_connection?
https://stackoverflow.com/questions/16082480/what-is-the-purpose-of-character-set-connection
这里有一段解释。意思是在做字符常量比较时,需要使用character_set_connection对常量进行转换。啥意思?
比如select * from text where name = '哈哈'这条语句。服务端首先会用character_set_client解码,然后处理语句。里面的where查询中的name列,会按照该列对应的字符集转码,但是对于常量‘哈哈’怎么处理?显然如果不处理,那么会按照character_set_client处理,但这可能有问题啊,可能会导致参与比较的双方使用的字符集都不一样,这还怎么比?所以就需要再按照character_set_connection来转换一次常量,所以character_set_connection貌似应该与比较的列名的字符集一致。
这里有三种字符集需要设置?其实可以只用如下命令:
set names xxx
便可以同时设定如上三个字符集。
几个例子:
默认都是utf8编码。
create table text (
name varchar(10)
) engine=Innodb default charset=utf8;
mysql> insert into text values('我');
Query OK, 1 row affected (0.01 sec)
建表并插入一条数据;
mysql> set character_set_results=gbk;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from text;
+------+
| name |
+------+
| �� |
+------+
1 row in set (0.00 sec)
这里设置了character_set_results为不同的编码,导致查询结果乱码;
mysql> set character_set_connection = ascii;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from text where name = '我';
Empty set, 1 warning (0.00 sec)
这是设置了character_set_connection,导致无法查到数据;