1、简介

1.1 字符集

1.1.1 字节和字符

(1)字符
字符是面向人类的描述符号,字符可以分为两种:

  • 可见字符:就是能打印出来的字符,比如’a’,’b’等;
  • 不可见字符:就是显示不出来的字符,比如回车符、tab等。

(2)字节
字节是面向计算机的,字符在计算机中通常以二进制或者十六进制的方式存储,以二进制为例就是通过多个0/1的比特位存储,字节可以看做是存储字符的单位。在不同编码方案中,一个字符可以由一个或者多个字节存储,使用的字节数越多,意味着能表示的数值范围越大,但是消耗的存储空间也越大。但是一个字节对应8个比特位是一定的,比特位就是0/1。
1字节即1B(Byte),1KB = 1024B = 1024字节,1B = 8bit。

1.1.2 字符编码

将一个字符映射成一个二进制数据的过程叫做编码,将一个二进制数据映射成一个字符的过程叫做解码字符集就是用来描述字符范围的编码和解码规则,即字符集规定了字符通过字节映射到二进制数据的规则。
字符集的种类有很多种,根据表示一个字符使用的字节数量是否固定,字符集分为以下两种:

  • 固定长度的编码方案:表示不同的字符所需要的字节数量是相同的,比方说ASCII编码方案固定采用1个字节来编码一个字符,ucs2固定采用2个字节来编码一个字符;
  • 变长的编码方案:表示不同的字符所需要的字节数量是不相同的,比方utf8编码方案采用1~3个字节来编码一个字符,gb2312采用1~2个字节来编码一个字符。

常见的字符集如下:

字符集名称 是否是变长编码 说明
ASCII 共收录128个字符,包括空格、标点符号、数字、大小写字母和一些不可见字符。由于总共才128个字符,所以可以使用1个字节来进行编码。
ISO 8859-1 共收录256个字符,是在ASCII字符集的基础上又扩充了128个西欧常用字符(包括德法两国的字母),也可以使用1个字节来进行编码。这个字符集也有一个别名latin1
GB2312 收录了汉字以及拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母。其中收录汉字6763个,其他文字符号682个。同时这种字符集又兼容ASCII
字符集。
GBK GBK字符集只是在收录字符范围上对GB2312字符集作了扩充,编码方式上兼容GB2312
utf8 收录地球上能想到的所有字符,而且还在不断扩充。这种字符集兼容ASCII字符集,采用变长编码方式,编码一个字符需要使用1~4个字节。

1.1.3 mysql中的字符集

1.1.2小节介绍的utf8字符集表示一个字符需要使用1~4个字节,在mysql中字符集表示一个字符所用最大字节长度在某些方面会影响系统的存储和性能,所以对于utf8字符集mysql做了如下处理:

  • **utf8mb3**:阉割过的utf8字符集,只使用1~3个字节表示一个字符;
  • **utf8mb4**:正宗的utf8字符集,使用1~4个字节表示一个字符。

mysql支持多种字符集,查看当前mysql中支持的字符集可以用以下语句:

  1. SHOW (CHARACTER SET|CHARSET) [LIKE 匹配的模式];

其中CHARACTER SETCHARSET是同义词,用任意一个都可以。

Maxlen表示mysql中不同的字符集表示一个字符最多需要多少个字节,如下:

字符集名称 Maxlen
ascii 1
latin1 1
gb2312 2
gbk 2
utf8 3
utf8mb4 4

1.2 比较规则

在确定了字符集后,如何比较该字符集下两个字符大小呢?即字符排序规则是什么样的呢?比较规则就是定义这个的。
查看mysql中支持的比较规则的命令如下:

  1. SHOW COLLATION [LIKE 匹配的模式];

2、各级别的字符集和比较规则

MySQL有4个级别的字符集和比较规则,分别是:

  • 服务器级别;
  • 数据库级别;
  • 表级别;
  • 列级别。

    2.1 服务器级别

    MySQL提供了两个系统变量来表示服务器级别的字符集和比较规则:
系统变量 描述
character_set_server 服务器级别的字符集
collation_server 服务器级别的比较规则

服务器级别默认的字符集是utf8,默认的比较规则是utf8_general_ci

2.2 数据库级别

MySQL提供了两个系统变量来表示数据库级别的字符集和比较规则:

系统变量 描述
character_set_database 当前数据库的字符集
collation_database 当前数据库的比较规则

我们在创建和修改数据库的时候可以指定该数据库的字符集和比较规则,具体语法如下:

  1. CREATE DATABASE 数据库名
  2. [[DEFAULT] CHARACTER SET 字符集名称]
  3. [[DEFAULT] COLLATE 比较规则名称];
  4. ALTER DATABASE 数据库名
  5. [[DEFAULT] CHARACTER SET 字符集名称]
  6. [[DEFAULT] COLLATE 比较规则名称];

举例:

  1. mysql> CREATE DATABASE charset_demo_db
  2. -> CHARACTER SET gb2312
  3. -> COLLATE gb2312_chinese_ci;

需要注意的一点是: character_set_database collation_database 这两个系统变量是只读的,我们不能通过修改这两个变量的值而改变当前数据库的字符集和比较规则。

2.3 表级别

我们也可以在创建和修改表的时候指定表的字符集和比较规则,语法如下:

  1. CREATE TABLE 表名 (列的信息)
  2. [[DEFAULT] CHARACTER SET 字符集名称]
  3. [COLLATE 比较规则名称]]
  4. ALTER TABLE 表名
  5. [[DEFAULT] CHARACTER SET 字符集名称]
  6. [COLLATE 比较规则名称]

举例:

  1. mysql> CREATE TABLE t (
  2. -> col VARCHAR(10)
  3. -> ) CHARACTER SET utf8 COLLATE utf8_general_ci;

2.4 列级别

需要注意的是,对于存储字符串的列,同一个表中的不同的列也可以有不同的字符集和比较规则。我们在创建和修改列定义的时候可以指定该列的字符集和比较规则,语法如下:

  1. CREATE TABLE 表名(
  2. 列名 字符串类型 [CHARACTER SET 字符集名称] [COLLATE 比较规则名称],
  3. 其他列...
  4. );
  5. ALTER TABLE 表名 MODIFY 列名 字符串类型 [CHARACTER SET 字符集名称] [COLLATE 比较规则名称];

举例:

  1. ALTER TABLE t MODIFY col VARCHAR(10) CHARACTER SET gbk COLLATE gbk_chinese_ci;

2.5 小结

由于字符集和比较规则是互相有联系的,如果我们只修改了字符集,比较规则也会跟着变化,如果只修改了比较规则,字符集也会跟着变化,具体规则如下:

  • 只修改字符集,则比较规则将变为修改后的字符集默认的比较规则;
  • 只修改比较规则,则字符集将变为修改后的比较规则对应的字符集。

各级别字符集和比较规则小结如下:

  • 如果创建或修改列时没有显式的指定字符集和比较规则,则该列默认用表的字符集和比较规则;
  • 如果创建表时没有显式的指定字符集和比较规则,则该表默认用数据库的字符集和比较规则;
  • 如果创建数据库时没有显式的指定字符集和比较规则,则该数据库默认用服务器的字符集和比较规则。

    3、客户端和服务端通信过程中字符集的转换

    3.1 mysql c-s通信过程中涉及的3个系统变量

    如果编码和解码使用的字符集不一致,可能导致乱码产生。mysql从客户端发送请求到服务端和服务端返回结果给客户端的过程中,并不是使用一种字符集编码方式,而是使用到了三个系统变量,如下:
系统变量 描述
character_set_client 服务器解码请求时使用的字符集
character_set_connection 服务器处理请求时会把请求字符串从character_set_client
转为character_set_connection
character_set_results 服务器向客户端返回数据时使用的字符集

这几个系统变量的默认值都是utf8,且为了避免各种字符集的转换,上面3个系统变量对应的编码方式通常设置成一致的,如果要修改,可以使用一下命令同时将上面3个系统变量的编码方式修改:

  1. SET NAMES 字符集名;

3.2 字符集转换

如果接收0xE68891这个字节串的程序按照utf8字符集进行解码,然后又把它按照gbk字符集进行编码,最后编码后的字节串就是0xCED2,我们把这个过程称为字符集的转换,也就是字符串'我'utf8字符集转换为gbk字符集,即:
字符集转换概念.png

3.3 mysql c-s通信过程中字符集的转换

过程如下图所示:
Mysql中的字符集和比较规则 - 图2
过程文字描述:

  1. 客户端使用操作系统的字符集编码请求字符串,向服务器发送的是经过编码的一个字节串;
  2. 服务器将客户端发送来的字节串采用character_set_client代表的字符集进行解码,将解码后的字符串再按照character_set_connection代表的字符集进行编码;
  3. 如果character_set_connection代表的字符集和具体操作的列使用的字符集一致,则直接进行相应操作,否则的话需要将请求中的字符串从character_set_connection代表的字符集转换为具体操作的列使用的字符集之后再进行操作;
  4. 将从某个列获取到的字节串从该列使用的字符集转换为character_set_results代表的字符集后发送到客户端;
  5. 客户端使用操作系统的字符集解析收到的结果集字节串。

注意:

  • 服务器认为客户端发送过来的请求是用character_set_client编码的。假设你的客户端采用的字符集和 character_set_client 不一样的话,这就会出现意想不到的情况。比如我的客户端使用的是utf8字符集,如果把系统变量character_set_client的值设置为ascii的话,服务器可能无法理解我们发送的请求,更别谈处理这个请求了;
  • 服务器将把得到的结果集使用character_set_results编码后发送给客户端。假设你的客户端采用的字符集和 character_set_results 不一样的话,这就可能会出现客户端无法解码结果集的情况,结果就是在你的屏幕上出现乱码。比如我的客户端使用的是utf8字符集,如果把系统变量character_set_results的值设置为ascii的话,可能会产生乱码;
  • character_set_connection只是服务器在将请求的字节串从character_set_client转换为character_set_connection时使用,它是什么其实没多重要,但是一定要注意,该字符集包含的字符范围一定涵盖请求中的字符,要不然会导致有的字符无法使用character_set_connection代表的字符集进行编码。比如你把character_set_client设置为utf8,把character_set_connection设置成ascii,那么此时你如果从客户端发送一个汉字到服务器,那么服务器无法使用ascii字符集来编码这个汉字,就会向用户发出一个警告。