《MySQL是怎样运行的》总结三

250 阅读12分钟

第三章、字符集和比较规则

前言:文章是根据《MySQL是怎样运行的:从根儿上理解 MySQL》这本书整理而来,作者小孩子4919。上一章介绍了MySQL系统的一些变量和启动选项,这一章将要介绍MySQL的常用字符集和比较规则。MySQL因为要经常存取数据,所以理解它使用到的字符集,以及服务器和客户端如何传递数据还是非常有必要的。本章就来揭开MySQL字符集的面纱。

第一节、字符集和比较规则介绍

1、字符集简介

众所周知,电脑存储数据是采用二进制的方式,那么要保存一个字符串,计算机是怎么实现的呢?这就需要建立二进制数据的映射关系了。

image-20210924100602557

要明确这么几个内容:

  • 字符范围:到底把哪些字符映射成二进制数据,然后我们就可以用哪些了,比如上面要是想表示个cba肯定不行,因为根本没有c的映射
  • 将字符映射成二进制数据叫做编码,从二进制数据映射成字符叫做解码

2、比较规则简介

字符与字符之间的比较肯定会经常出现,如果想对某个数据进行排序,比如一个是ab,一个是aA,那么谁先谁后呢,很明显十六进制一个是0x0110,一个是0x0111,ab更小一些,这就是比较规则。

现实当中很多时候都是不区分大小写的,那么就可以先把两个不同数据的字符全都转换成大写,然后再进行比较。

3、一些重要的字符集

  • ASCII字符集

    共收录128个字符,包括空格、标点符号、数字、大小写字母和一些不可见字符。由于总共才128个字符,所以可以使用1个字节来进行编码,我们看一些字符的编码方式:

    'L' ->  01001100(十六进制:0x4C,十进制:76'M' ->  01001101(十六进制:0x4D,十进制:77
  • ISO 8859-1字符集

    共收录256个字符,是在ASCII字符集的基础上又扩充了128个西欧常用字符(包括德法两国的字母),也可以使用1个字节来进行编码。这个字符集也有一个别名latin1

  • GB2312字符集

    收录了汉字以及拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母。其中收录汉字6763个,其他文字符号682个。同时这种字符集又兼容ASCII字符集,所以在编码方式上显得有些奇怪:

    • 如果该字符在ASCII字符集中,则采用1字节编码。
    • 否则采用2字节编码。

    这种表示一个字符需要的字节数可能不同的编码方式称为变长编码方式。比方说字符串'爱u',其中'爱'需要用2个字节进行编码,编码后的十六进制表示为0xCED2'u'需要用1个字节进行编码,编码后的十六进制表示为0x75,所以拼合起来就是0xCED275

    小贴士: 我们怎么区分某个字节代表一个单独的字符还是代表某个字符的一部分呢?别忘了ASCII字符集只收录128个字符,使用0~127就可以表示全部字符,所以如果某个字节是在0~127之内的,就意味着一个字节代表一个单独的字符,否则就是两个字节代表一个单独的字符。

  • GBK字符集

    GBK字符集只是在收录字符范围上对GB2312字符集作了扩充,编码方式上兼容GB2312

  • utf8字符集

    收录地球上能想到的所有字符,而且还在不断扩充。这种字符集兼容ASCII字符集,采用变长编码方式,编码一个字符需要使用1~4个字节,比方说这样:

    'L' ->  01001100(十六进制:0x4C'啊' ->  111001011001010110001010(十六进制:0xE5958A

    小贴士: 其实准确的说,utf8只是Unicode字符集的一种编码方案,Unicode字符集可以采用utf8、utf16、utf32这几种编码方案,utf8使用1~4个字节编码一个字符,utf16使用2个或4个字节编码一个字符,utf32使用4个字节编码一个字符。更详细的Unicode和其编码方案的知识不是本书的重点,大家上网查查哈~ MySQL中并不区分字符集和编码方案的概念,所以后边唠叨的时候把utf8、utf16、utf32都当作一种字符集对待。

对于同一个字符,不同字符集也可能有不同的编码方式。比如对于汉字'我'来说,ASCII字符集中根本没有收录这个字符,utf8gb2312字符集对汉字的编码方式如下:

  • utf8编码:111001101000100010010001 (3个字节,十六进制表示是:0xE68891)
  • gb2312编码:1100111011010010 (2个字节,十六进制表示是:0xCED2)

第二节、MySQL支持的字符集和比较规则

1、utf8和utf8mb4

MySQL中使用utf8mb3和utf8mb4:

  • utf8mb3:也就是我们经常说的utf8,它只使用1-3个字节表示字符
  • utf8mb4:就是我们常说的utf8mb4,使用1-4个字节表示字符

2、查看字符集

可使用如下语法:

showcharacter set|charset】 [like 匹配条件];

查询如下(没什么用,非常多,大多数都用不到):

image-20210924104336701

其中几个常用字符集的Maxlen列列出,这个比较有用,需要记住:

字符集名称Maxlen
ascii1
latin11
gb23122
gbk2
utf83
uft8mb44

3、查看比较规则

可使用如下语法:

show collation [like 匹配条件];

utf8的比较规则:

image-20210924104856472

不难发现它的最后都是以ci为结尾的,是不区分大小写的意思,详细的后缀含义如下表:

后缀英文释义含义
_aiaccent insensitive不区分重音
_asaccent sensitive区分重音
_cicase insensitive不区分大小写
_cscase sensitive区分大小写
_binbinary以二进制比较

上面写了default的utf8_general_ci是默认的比较规则。

第三节、字符集和比较规则的应用

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

一共有四个级别的字符集和比较规则,分别是服务器级别、数据库级别、表级别、列级别。

(1)服务器级别

两个系统变量如下:查询结果

系统变量描述
character_set_server服务器级别的字符集
collation_server服务器级别的比较规则
image-20210924105615979

这里既然是服务器级别的,我们可以在启动选项(配置文件中)或者服务器的命令行来设置。这样服务器启动就能搜到我们自己的配置了。

(2)数据库级别

数据库级别的,可以在创建数据库或者修改数据库时设置:

#设置
create database 数据库名
	[[default] character set 字符集名称]
	[[default] collate 比较规则名称];
	
#修改
alter database 数据库名
	[[default] character set 字符集名称]
	[[default] collate 比较规则名称];
	
#其中default可以省略
image-20210924110523817

如果想查看当前数据库的字符集和比较规则,可以使用如下两个系统变量:

系统变量描述
character_set_database服务器级别的字符集
collation_database服务器级别的比较规则
image-20210924110856983

注意:

这两个变量只是告诉我们当前数据库的字符集和比较规则的值,我们不能通过修改这两个变量的值来改变当前数据库的字符集和比较规则。

(3)表级别

创建修改表时指定字符集和比较规则:

create table 表名(列的信息)
	[[default] character set 字符集名称]
	[collate 比较规则名称];
	
#修改
alter table 表名
	[[default] character set 字符集名称]
	[collate 比较规则名称];
	
#其中default可以省略
image-20210924112122772

(4)列级别

同一个表中不同列也可以有不同字符集和比较规则,语法:

#创建表时指定列的字符集和比较规则
create table 表名(
	列名 字符串类型 [character set 字符集名称] [collate 比较规则名称]
	其他列...
)

#修改列的字符集和比较规则
alter table 表明 modify 列名 字符串类型 [character set 字符集名称] [collate 比较规则名称];

注意:这里有点问题,比如我们存储的是utf8字符集的数据,如果改成ascii字符集,不支持汉字,那么到时候就会出错

(5)字符集与比较规则的联系

  • 字符集和比较规则是相互联系的,它们类似配套使用,如果修改了字符集,比较规则也随之修改,反之同理,上面四个级别无论哪个级别都适用。
  • 如果创建修改列没设置字符集和比较规则,那么采用表的。
  • 如果创建表没设置字符集和比较规则,那么采用数据库的。
  • 如果创建数据库没设置字符集和比较规则,那么采用服务器的。

2、客户端和服务器通信的字符集

(1)为什么会乱码

这个应该不难理解,当一个程序A使用字符集①去编码某个字符串,然后把这个字节序列(比如是16位)传给程序B,然后B用不一样的字符集②去节码,那大概率就乱码了,因为字符集的对照根本不同,这就是字符集转换了。

image-20210924114906924

(2)MySQL的请求和响应

客户端发送请求

MySQL客户端和服务器之间的请求和响应是遵守了一定格式的,这就叫做MySQL通信协议。

一般情况下,客户端编码请求字符串时使用的字符集与操作系统当前使用的字符集是一致的。Windows系统下,字符集称为代码页,可以在dos窗口上面右键,点击属性,弹出这个窗口,看到当前代码页为GBK,前面有个数字,每个代码页对应一个唯一的数字,936是GBK,65001代表UTF-8。

image-20210924115742400

或者也可以在命令行输入chcp查询。

image-20210924120002707

当然我们也可以修改,通过修改default-character-set启动选项,来设置客户端以什么字符集来编码。

#语法
mysql --default-character-set=utf8;

上面说的这些是客户端发送请求的字符集是什么,以及如何配置。

服务器接收请求

本质上讲,服务器接收的就是个字节序列,服务器将这个字节序列看作是使用系统变量character_set_client代表的字符集进行编码的字节序列(客户端和服务器连接后,服务器会维护一个session级别的变量character_set_client)。

客户端把字节序列采用编码字符集来处理,服务器接收这个编码后的字节序列,把它按照编码字符集来接收,这两个编码字符集是独立的,但我们一般会让它们尽量一致。

好比我和你说name,我说的是英文名字的意思,你非要按照中文的方式来接收也没问题,但是你解码后可能得到的就是na(那)me(么)了。

服务器处理请求

当前服务器会按照character_set_client值这个字符集来接收字节序列,但是处理这个字节序列却不一定按照这个字符集来处理,而是将其转换为session级别的character_set_connection对应的字符集来编码这个字节序列。比如我们接收了一个name,我们知道它是英文了,我们会按照英文name处理这个单词吗,不一定我们可能按照中文的方式把它翻译为名字,当然我们亦可以通过修改character_set_connection来改变这个系统变量。

这里还有一个系统变量是比较规则collation_connection,这个可以指定比较规则。

服务器生成响应

我们是不是直接把服务器处理好的请求直接发送给客户端呢?这时就要看session级别的系统变量character_set_results的值了,服务器会把结果转换成character_set_results对应字符集的字节序列,然后发送给客户端,这里character_set_results值也是可以设置的。

客户端接收响应

客户端接收的也是一个字节序列,这个时候客户端会使用当前客户端默认的字符集来解释这个字节序列。

三个关键参数

系统变量描述
character_set_client服务器认为请求是按照这个系统变量指定字符集编码的
character_set_connection服务器处理请求时,会把character_set_client字节序列转换成character_set_connection
character_set_results服务器采用该系统变量指定的字符集把字节序列返回给客户端

3、比较规则的应用

比较规则不同,可能拿到的结果顺序也不同,所以如果结果的顺序和我们想象的不一样,可以考虑是不是比较规则用的不恰当。