在文章开始前,大家可以先考虑几个问题,这样方便更快理解文章的知识点,下面的问题都会在文章中找到答案哦!
1、字符集、编码、解码的概念是否真的理解?
2、常见的字符集如UTF-8,GBK等存在什么差异?
3、数据库中如何设置字符集类型?
4、什么是比较规则,数据库中如何设置比较规则类型?
5、什么是乱码,为什么会产生乱码?
6、MySQL中UTF-8、UTF-8mb3、UTF-8mb4有什么区别?
学习过计算机的都知道,计算机只能存储二进制的数据即0和1,那平常我们使用电脑中展示的表情包、字符串等数据计算机是如何识别,进行存储的呢?
显而易见,是建立非二进制数据(如:表情包、字符串)和二进制数据(0和1)之间的映射关系,通过它们的映射关系,我们能够进行相互转换,实现与计算机之间得交互,当存储到计算机中时则转换成对应的二进制数据,当需要在电脑展示时则转换成非二进制数据。但是,建立这种映射关系我们需要考虑哪些问题呢?
如需建立两者的映射关系,我们需要考虑以下的问题:
1、确定建立映射的字符范围即:哪些字符是需要和二进制数据建立映射?
2、如何实现两者之间的映射即:映射的规范是什么?
其实,上面的这两个问题我们平常的计算机使用者是无法接触到的,字符集的制定是有专门的组织如ISO/IEC等来进行制定的,制定后经过实践是可行的才会应用到计算机中。
虽然,这个过程我们是无法参与,但是,我们要知道它们产生的原因,因为不同区域是使用不同的语言进行交流,视觉上的体现就是使用不同的符号进行沟通,如美国,英国等地方就是使用26个字母、数字等字符进行交流,而我们国家则会存在更加复杂的各式各样的文字,所以,字符集是为了适应不同区域通过计算机之间完成交流而产生的。
在更进一步认识字符集之间,需要了解一些相关概念。
可以理解为某些字符组成的一个集合,集合中由哪些字符组成是由制定这个字符集的协会来决定。
字符编码则是:将字符集中的字符映射为特定的字节或者字节序列,它表示的是一种规则。通常特定的字符集采用特定的编码方式(即一种字符集对应一种字符编码,如: ASCII、ISO-8859-1、GB2312、GBK都是表示了字符集又表示了对应的字符编码,但Unicode字符集是特例,它对应的字符编码有:UTF-8、UTF-16、UTF-32等)
如:我们自定义一个my字符集和字符编码,它的一个规则如下:
1、包含的字符:'h'、'l','o','e'
2、编码规则: 一个字符需要一个字节编码(注: 1byte(字节) = 8bit,1bit对应的就是一个0或者1),则字符和字节的映射关系如下:
通过上面的映射关系我们知道my字符集可以组成许多不一样的字符,具体如下:
将一个字符映射成二进制数据的过程叫做编码,如:'a' => 0000 00001
将一个二进制数据映射成一个字符的过程叫做解码,如:0000 00001 => 'a'
通过上面的基础规则学习,我们已经对字符集、编码、解码等基础知识有了基本的认识。现在我们就通过图形化来举一个例子更加形象理解的这些知识(以:ASCII字符集为准,用我们编程入门的最常见的字符串:hello world为例子)
编码: 在屏幕输入文字 -> 根据指定编码类型 -> 将输入的文字编码成计算机能够识别的二进制数 -> 计算机存储编辑成的二进制数值
解码: 计算机读取存储的二进制数值 -> 根据指定的解码类型解码 -> 将二进制数值解码成字符集中表达的字符 -> 在屏幕显示
⼀个字符在编码需要的字节数可能不同的编码⽅式称为变长编码⽅式,比如GB2312编码方式,因为GB2312字符集兼容了ASCII字符,所以当一个字符串中有的字符属于ASCII字符集中的字符时,它编码时需要占用的字节数为1,如果字符属于ASCII字符集中没有的,则编码时需要占用2个字节。
举例说明: 比如字符串'日b',其中'日'不属于ASCII字符集中的字符,需要⽤2个字节进行编码,假设编码后的⼗六进制表示为0xCD2,'u'属于ASCII字符集的字符,需要⽤1个字节进⾏编码,假设编码后的⼗六进制表示为0x15,所以拼合起来就 是0xCD215。
ASCII、GB2312、Unicode、GBK等
ASCII字符集: 全称《美国信息交换标准代码》,主要用于显示现代英语和其它西欧语言,主要包括:可显示字符(英文字母、阿拉伯数值、标点符号)、以及控制字符(回车、换行、退格等特殊字符)。
ASCII编码: 使用一个字节编码,美国定制的交换标准,目的是将ASCII字符集包含的字符转换成计算机能够识别的二进制(0和1),它是最通用的信息交换标准,到目前为止总共定义了128个字符。
ASCII编码缺点: 只能显示26个基本拉丁字母、阿拉伯数目字和英式标点符号,因此只能用于显示现代美国英语(而且在处理英语当中的外来词如naïve、café、élite等等时,所有重音符号都不得不去掉,即使这样做会违反拼写规则),而且对其他的语言支持力度也不大,所以现在苹果也使用Unicode替换ASCII。
别名latin1,使用一个字节编码,兼容ASCII字符集,是在ASCII字符集的基础上⼜扩充了128 个⻄欧常⽤字符(包括德法两国的字⺟),共收录256个字符。
(1) GB2312字符集:
(2)GBK字符集
(3)GB18030字符集
(1) UTF-8字符编码:
(2) UTF-16字符编码:
(3) UTF-32字符编码:
更多关于字符集的知识,可以到《全网最全面、全详细的编码、解码知识!!!》查看。
通过上文我们知道字符集就是包含了多个字符组合的一个集合,既然存在这样一个集合,那它们的是如何比较不同字符的大小呢?这里就需要我们的第二位主角-比较规则大佬登场了
比较规则: 字符集中字符的一个比较大小的规则,一个字符集中可以存在多个比较规则。最常见的比较方式就是字符映射后的二进制比较,英文名也叫binary collation,如:字符a对应的二进制为0000 0001,字符b对应的二进制为0000 0010,,则我们可以说在这种规则下字符b大于字符a。
通过上面的介绍,大家应该已经对字符集和比较规则的一些概念有了大致的了解,下面我们就开始结合MySQL来进行真正的实战。
查询命令: show charset或者character set like '字符集的名称'
(一)、通过下图可知,MySQL数据库默认支持41种字符集,下图关键词的含义:
1、Charset列为字符集的名称
2、Default collation列为字符集对应的默认比较规则(因为一种字符集是可以存在多种比较规则的)
3、Maxlen列则表示这种字符集编码(表示)一个字符时最多需要多少个字节。
(二)、常用的字符集一个字符编码需要的最大字节数
在上面的截图中,可能有人会有疑问,为什么mysql会支持两种utf-8(即utf-8和utf-8mb4)类型的编码?它们之间存在什么区别?
我们在上面介绍Unicode字符集时有说到,其实utf-8,utf-32都是属于Unicode字符集的一种编码方案,utf-8编码方案表示一个字符需要1~4个字节,但是,在实际生活中常用的字符实际上只需要3个字节即可表示,在数据库中,表示字符的大小会影响到数据存取的性能,所以MySQL数据库的设计者就定义类utf-8和utf-8mb4两套方案。
1、utf-8也叫utf-8mb3: 代表一个字符只需要1~3个字符。
2、utf-8mb4: 代表一个字符需要使用1-4个字符,也就是我们平常说的真正的utf-8编码。
区别: 在MySQL数据库中,utf-8是utf-8mb3的别名,它是使用1~3个字节来表示一个字符的,如果需要使用4个字节表示一个字符的,如存储emoji表情包,需要使用utf-8mb4编码方案。
查询命令: show collation like '比较规则名(可以模糊查询)'
(一)、由下图可知,现在MySQL数据库支持222种比较规则,每种字符集可能存在多种比较规则,它们的规律如下:
1、比较规则都是以字符集的名称开头,如下图2中的utf-32比较规则都属于utf-32字符集。
2、下划线的第二个单词表示的是该比较规则是使用于哪种语言,如:utf-32_spanish_ci则表示使用西班牙语的规则比较,utf-32_general_ci则表示是一种通用的比较规则。
3、结尾的单词表示是否区分不同语言中的重音、大小写等策略,常见的结尾词语含义如下:
每种字符集对应可能存在多种比较规则,所以在不指定具体比较规则的时候,字符集会有自己默认的一个比较规则,使用show collation查询出来的比较规则中,default列值为yes的表示属于该字符集的默认比较规则,如上图中utf-32的比较规则就是utf32_general_ci。
MySQL数据库中,支持4个层级的方式来设置数据库的字符集和比较规则,范围从大到小分别是: MySQL服务器级别、数据库级别、表级别、列级别。
MySQL保留了以下两个关键字来设置服务器级别的字符集类型和比较规则,具体含义如下:
关键字 | 具体含义 |
---|---|
character_set_server | 服务器级别的默认字符集类型 |
collation_server | 服务器级别的默认字符集比较规则 |
我们可以通过下面的两条命令来查询MySQL服务器级别默认的字符集类型和比较规则
1、查询默认的字符集类型: shwo variabkes like 'character_set_server';
2、查询默认的比较规则类型: shwo variabkes like'collation_server';
如果我们想修改默认的服务器级别的字符集类型和比较规则,可以在MySQL启动的配置文件my.ini或者直接通过set对应的变量达到修改的目的。
方式1: 修改MySQL启动配置文件配置(直接到MySQL安装路径下找my.ini)然后添加以下的属性即可
character_set_server=utf-32
collation_server=utf-32_chinese_ci
方式2: 连接数据库后直接通过set设置这两个变量的值,但是这个设置只针对本次客户端连接,如果退出了连接则会还原默认的,下一次连接还是系统之前默认的值。
总结: 上面我们说到,每个字符集都有一个默认的比较规则,它们之间是相互存在联系的,无论我们修改它们两个中的哪一个,另外一个也会跟着改变既:只修改字符集,则比较规则将变为修改后的字符集默认的⽐较规则,只修改比较规则,则字符集将变为修改后的比较规则对应的字符集。
数据库中存在的两个变量(character_set_database、collation_database)用于描述数据库的字符集和比较规则,但是它们都是只读的,没法通过修改这两个变量的值来修改数据的字符集和比较规则,想要修改数据库的字符集和比较规则,需要在创建或者修改数据库的时手动指定character set和collation变量的值,如果不指定的话,则默认使用服务器级别的字符集和比较规则。
指定数据库字符集和比较规则的语法:
// 创建数据库时指定字符集和比较规则
create database 数据库名 [[default] character set 字符集名称] [[default] collate ⽐较规则名称];
// 修改数据库的字符集和比较规则
alter database 数据库名 [[default] character set 字符集名称]
查询数据库字符集和比较规则的语法:
关键字 | 具体含义 |
---|---|
character_set_database | 数据库的字符集 |
collation_database | 数据库的⽐较规则 |
在创建或者修改数据表的时候,我们可以根据自己需要去修改数据表对应的字符集和比较规则,但是如果不指定的话,则默认使用数据库级别的字符集和比较规则,具体的语法如下:
// 创建数据表时指定字符集和比较规则
create table 表名 (列的信息) [[default] character set 字符集名称] [collate ⽐较规则名称]]
// 修改数据表的字符集和比较规则
alter table 表名 [[default] character set 字符集名称] [collate ⽐较规则名称]
在创建或者修改表中字段的时候,我们可以根据自己需要去指定表中某些列的字符集和比较规则,但是如果不指定的话,则默认使用表级别的字符集和比较规则,具体的语法如下:
// 创建数据表时指定某列的字符集和比较规则
create table 表名( 列名 字段类型 [character set 字符集名称] [collate ⽐较规则名称], ... );
// 创建数据表时指定某列字符集和比较规则
alter table 表名 modify 列名 字段类型 [character set 字符集名称] [collate ⽐较规则名称];
通过上面的学习,我们可以准确的知道某个数据库、某个表、某个字段对应的字符集类型和比较规则,我们就可以预估出存储在数据库中一条数据占用的大小,这样对我们进行优化有一定帮助(因为网络传输的带宽是有限制的,一次如果查询太多数据,查询的性能会影响,通过预估每条数据占用的存储时间,可以帮助我们进一步确定返回数据条数的限制,是否需要分页等问题)。除此之外,出现乱码得时候我们也能根据对应得编码来进行排查
如demo表中name字段使用的字符集是gbk,,存储的数据如下,根据gbk字符集存储数据占用的字节数最大为2可知(上文介绍过,可以使用:show charset命令查看数据支持的字符集和它占用的字节数),在demo表中下面这条数据占用的存储空间为6个字节,如果列的字符集为utf-8则存储空间为9个字节。
根据需要我们进行MySQL的服务级别、数据库级别、数据表级别、列级别进行设置字符集和比较规则,如果是直接使用语句进行修改的话则只对本次连接起作用,退出连接后又恢复到默认的字符集和比较规则,如果想要编程默认的,则可以直接在mysql的配置文件添加对应的变量设置即可。
一个字符集可以存在多种比较规则,所以每种字符集都会有一个默认的比较规则(可以使用show charset命令查看),因为字符集和比较规则是相互联系的,所以只修改字符集,则⽐较规则将变为修改后的字符集默认的⽐较规则,只修改⽐较规则,则字符集将变为修改后的⽐较规则对应的字符集。
服务级别、数据库级别、数据表级别、列级别的字符集和比较规则的范围是从大到小的,所以它们之间有以下的规则:
了解了字符集和编码方案知识后,我们知道不同的字符集有不同的编码,不同的编码方案占用的字节数也是不一样的,如果操作不当,就会出现让人无法理解的后果(乱码),总结起来,出现乱码的情况以下两种:
1、编码过程和解码过程使用的编码方式不一致。试想一下,如果你跟棒子交流说的是汉语,棒子却用韩语字典查询翻译,最后棒子能够懂你的意思?很明显,如果编码和解码使用的方式不同,最后得到的结果会是南辕北辙。
2、编码/解码对应的字符集不存在对应的字符。比如ASCII编码中只包含有128个字符,没有繁体字,如果你使用ASCII编码方案去解码繁軆字,最后能够得到正确结果?很明显,结果得到的也是我们无法理解的一些字符串。
一个字符串从一种字符集类型转换成另外一种字符集。如字符串"菜鸡",编码类型属于utf-8,在utf-8编码中对应的二进制为0000 0001,在gbk编码中对应的二进制为0000 1000,要想转换成gbk字符集中的字符则需要进行以下两步操作:
1、先将字符串"菜鸡"按照gbk字符编码方案解码成为对应的二进制既0000 1000。
2、然后再按照gbk的编码方案编码成对应的字符串,此时得到的"菜鸡"字符串就属于gbk字符集中的一个字符串。
在MySQL中,客户端与服务端之间的请求响应最终的体现实际上就是字符序列。客户端将发送的内容根据编码方式编码成对应的字节序列,服务端接收后并进行一系列处理,然后将结果根据对应的编码方式编码并返回给客户端。
在开始讲解客户端与服务端字符集转换之前,需要先了解一些关于MySQL服务端编码的概念,如下
关键字 | 具体含义 |
---|---|
character_set_client | 客户端使用的编码类型,用于将接收到的字符串解码,既sql语句是使用什么编码的 |
character_set_connection | 服务器运⾏过程中使⽤的字符集 |
character_set_results | 服务器向客户端返回数据时使⽤的字符集 |
查询命令: show variables like 'character_set%'
下面让我们先来看下官方文档是如何描述客户端和服务端通信时字符集的转换的:
单单看官方文档,可能比较抽象,下面我们通过具体的图片来了解它们之间转换的具体步骤:
总结起来就是分为以下的五个步骤:
1、客户端将SQL语句按照character_set_client字符集对应的编码方案编码成字节序列,然后发送给服务端。
2、服务端接收到客户端发送的字节序列,将它通过character_set_connection字符集对应的编码方法进行编码,生成新的字节序列。
3、因为表中的列也有自己的字符集类型,且它的优先级是更高,所以需要将第2步的字节码再编码成列对应字符集的字节序列,然后去匹配具体的数据。
4、将匹配到的数据按照character_set_results字符集对应的编码方案编码成字节序列,并返回给客户端。
5、客户端接收到服务端响应的字节序列,按照character_set_client对应的字符集编码类型解码成对应的字符,展示到屏幕上,到此为止,一次请求和响应成功结束。
问题1:看到这里大家肯定会有疑问了,一次请求就要进行这么多次编码,有没有这个必要呢?
答案是肯定的,因为character_set_xxx这三个变量是可以设置为session级别和全局级别的,因此不同的客户端可能会不同的编码类型,必须有对应的参数来适应不同的类型,且表中的字段也有字符集类型,优先级更高,所以需要经过对应的编码和解码才能实现数据的转换。
问题二: 为什么有了character_set_client还要有character_set_connection,直接将character_set_client对应的字节序列转换成表中的列对应字符集字节序列不行?
答案:不行,因为MySQL官网说了,character_set_connection变量对应的字符集是要用于字符串字面值的转义的。
问题三: 什么是字符串字面值? 一个字符串字面值就是两个双引号之间的字符序列,如"hello world",上面说到的character_set_connection的作用就是用来转义这个字符串字面值的,如:select length("我爱你"),如果character_set_connection是utf-8mb3的话则会返回3,如果是utf-32的话则会返回12,也就是说它是根据不同的字符集类型有不同的转义。
1、通常来说,character_set_client、character_set_connection,character_set_results这三个变量的值都是保持一致的,这个就可以避免不同编码类型之间的转换。
2、如果我们直接在连接服务端的时候使用set命令设置这三个变量的值,那它只针对本次连接(会话级别),退出本次连接后又会恢复默认的值。使用: 【set names = 字符集类型】可以一次设置这三个变量的值。
3、如果想修改这三个变量的值为全局级别的,可以在mysql的配置文件my.ini中添加:default-character-set=字符集编码(如:utf-8)或者启动mysql客户端时指定这个参数,则可以修改它默认设置的值。
讲完字符集之间的关系,我们来讲点轻松的,比较规则的使用,相信排序这个功能大家没少用吧,平常我们都只是直接默认使用order by字段名,数据库就返回了排序好的数据给我们(如果没有学习过数据库的也可以看看,后续会进行数据库语法的讲解),你知道这个过程实际上是使用了哪个比较规则,如果需求变动有特殊的要求该怎么处理?
平常我们使用的最的就是UTF-8字符集,这里就通过UTF-8字符集来看比较规则的实际应用。
通过上图可以发现,默认是按照字典的顺序进行排序,如果我们有个需求需要根据转换之后的二进制进行排序呢,就该使用到我们上面介绍的修改字符集的比较规则来完成了。
1、通过show collation like '需要查询的比较规则或者字符集名'查询某种字符集支持的比较规则。
2、使用: alter table 表名 modify 列名 字符串类型 [character set 字符集名称] [collate ⽐较规则名称]
相信经过上面的学习,下次项目经理再给你提供奇葩排序,心里有底了吧!(先给钱,再加需求)!
《mysql是怎样运行的》
《全网最全的编码知识》
默默的说一句,能坚持看到最后的都是牛逼的人,为你们点赞! 相信此时你再回头去看前面的五个问题,心里应该知道答案是什么了吧,带着问题学习是否让你更加有效率?