第三章:乱码的前世今生-字符集和比较规则
字符集和比较规则简介
字符集简介
计算机中只能存储二进制,所以存储字符串就是建立和二进制数据的映射关系(二进制 《-》字符串),有以下两个核心:
(1)界定字符的范围:有哪些字符在这个字符集中
(2)如何映射?字符映射成二进制称之为编码,二进制转回字符叫做解码:也就是二进制和字符串的转换关系
字符集就是某些人类字符到二进制码的转换关系,比如这样:
定义一个字符集,然后它可以包含一些字符和字符对应的映射关系
有了这个字符集以后,就可以用这个字符集中的映射关系来表示一些字符串了:
比如
'bA' -> 0000001000000011 (十六进制:0x0203)
'baB' -> 000000100000000100000100 (十六进制:0x020104)
'cd' -> 无法表示,定义的字符集中不包含字符'c'和'd'
总结:字符集就是一些字符和字符到二进制的映射关系组成的集合:
具体就是包含的很多字符和它们与二进制之间的转换关系
字符集 = 字符 + 字符对应的编码规则
比较规则
有了字符和编码规则,可以通过直接比较两个字符的二进制编码大小来确定两个字符的大小
比如a的编码为0x01 ,b的编码为0x02,所以a小于b
这种比较方式叫做二进制比较规则
有些时候是不区分大小写的,因此这种比较规则不太适用了,不过可以进一步指定比较规则
(1)全部转换为大写或者小写
(2)再比较二进制编码的大小
但是字符不止英文字母这种,还有几万种汉字字符存在于生活中,需要制定多种比较大小的方式------同一种字符集可以有多种比较规则来满足不同的使用场景
总结:对于一个字符集可以制定多种不同的比较规则来应对不同的现实场景
一些比较重要的字符集
常用的字符集:
ASCII字符集:字母和各种符号
ISO 8859-1:在ASCII的基础上扩充了西欧常用字符
GB2312:收录了汉子以及拉丁文字等字符,兼容ASCII字符
GBK:对GB2312扩充,并兼容其编码格式
Utf8:收录地球上的所有字符,并且还在不断成长!!utf 8 yyds!!
MySQL中支持的字符集和排序规则
一般常用的字符只用1到3个字节就可以了,字符集中一个字符占用的最大字节长度会影响数据库的性能,因此MySQL的设计师定义了两个概念
(1)utf8mb3 : 阉割过的utf8字符集,只使用1到3个字节表示一个字符
(2)utf8mb4:正宗的utf8,使用1到4个字节表示字符
注意:MySQL中utr8就是utf8mb3的简称,所以如果要存储一些emoji什么的,还是使用utf8mb4比较好
个人思考:这里可以这样来记忆:utf8mb3,就是max bit = 3,这样以此类推,就知道utf8mb4就是最大字节数为4的正宗的utf8了,范围越大越普遍适用
字符集的查看
SHOW (CHARACTER SET|CHARSET)【LIKE 匹配的模式】
常用字符集的最大字节占用数:
比较规则的查看
SHOW COLLATION 【LIKE 匹配的模式】
每种字符集都有很多比较规则,也有默认的比较规则:Default列中的值YES就是默认的比较规则
字符集和比较规则的应用
各个级别的字符集和比较规则
4个级别一共有,分别是:
服务器级别 -》 数据库 -〉 表 -》 列
服务器级别
使用两个系统变量来表示服务器级别的字符集和比较规则:
Character_set_server : 服务器级别的字符集
collation_server:服务器级别的比较规则
默认一般是utf8,默认比较规则是utf8-general-ci
可以在启动时使用SET修改两个变量的值,也可以直接在配置文件中设定
数据库级别
创建和修改数据库的时候可以制定数据库的字符集和比较规则
CREATE DATABASE database-name
\[DEAFULT\]
查看下面的系统变量来知道当前数据库使用的字符集和比较规则:
创建数据库也可以不指定字符集和比较规则:
CREATE DATABASE database_name;
这样将直接使用服务器级别的字符集和比较规则作为数据库的字符集和比较规则
这个也很好理解:全局支配局部,一般默认都是全局优先
表级别
创建和修改的表的时候指定字符集和比较规则
语法如下:
CREATE TABLE table_name(column)
\[DEFAULT\] CHARACTER SET name
COLLATE name
ALTER TABLE name
\[DEFAULT\] CHARACTER SET name
COLLATE name
如果创建的表和修改表中的语句没有指定字符集和比较规则,那么直接使用所在数据库的字符集和比较规则。又是全局作为默认属性
列级别
对于一个表中的不同的列也可以自定义不同的字符集和比较规则
修改和创建的时候一样可以
CREATE TABLE name(
column_name string_type [CHARACTER SET name] [COLLATE]
other column....
);
ALTER TABLE name MODIFY column_name string_type [CHARACTER SET]
同样的,如果创建或者修改没有指定字符集和比较规则,那么是使用该列所在表的字符集和比较规则
注意:转换列的字符集的时候如果转换前列中的存储的数据不兼容转换后的字符集,那么就会出错
仅修改字符集或者比较规则
字符集和比较规则是有关联的,修改其中一者,那么与之关联的对象也会跟着变化
具体规则:
(1)只修改字符集,那么比较规则变为修改后字符集的默认规则
(2)只修改比较规则,则将字符集转换为修改后比较规则对应的字符集
客户端和服务器通信中的字符集
编码和解码使用的字符集如果不一样?
字符串在计算机中就是字节串,如果使用不同的字符集去解码,那么会得到很奇怪的结果
所以如果对于同个字符编码和解码,如果使用的字符集不一样,那就会产生类似于乱码的效果
比如说"我"这个字符,在utf8中的表示是0xE68891
如果使用gbk去解码:
会按照gbk的规则去理解,首先是0xE6,然后是0xE688,于是得到"鎴"加半个字符
这就是乱码产生的由来
总结:对于一个字符串最好使用它原先所在的字符集的解码规则来解码,否则会产生乱码效果
字符集转换的概念
将刚刚的"我"的utf8编码先按照原先的规则解码一次,得到原字符,再找gbk的字符集进行编码,这样得到的为0xCED2,这就是一次字符集的转换:将"我"从utf8转换到了gbk字符集
个人理解:就是先翻译成通用的机器语言Unicode码,再用其他编码规则编码,那么就得到了该字符在这个字符集下对应的编码了
MySQL中字符集的转换
客户端发往服务器的请求的本质就是一个字符串,服务器返回的结果本质也就是一个字符串。字符串就是某种格式的二进制编码。
发送请求到返回结果中间会有多次字符集的转换
使用到下面的三个系统变量:
流程如下:
(1)首先客户端发送一个字符请求,比如说"我"。注意客户端使用的字符集和操作系统是一致的
假设系统是utf8,则发送时就是0xE68891
注意:如果使用第三方的可视化工具和服务器交互,比如navicat,那么使用的就是第三方自定义的字符集了
(2)服务器接收到的就是与之对应的二进制字节,默认使用的字符集是客户端字符集,于是将二进制码转换成对应的编码也就是:0xE68891
个人思考:害害害!!学过机网还好知道,发送的时候消息全都会转成数字信号也就是二进制格式再转换为电信号进行发送!!所以服务器接收到就是二进制字节串了。那么再根据客户端使用的字符集的规则进行编码,就可以得到相应的编码了
然后按照utf8进行解码得到字符"我",然后再按照set_connection的字符集进行编码,得到字节串 0xCED2
总结:二进制 -》 0xE68891 -〉 "我" -》 0xCED2
其实就是将字符转换为服务器端的字符集的字节串的形式,只是中途多经历一次二进制的转换而已
(3)然后根据这个字节串去相关的表中查找记录
注意如果表中的列的字符集不一致的话,还需要再进行一次转换
(4)找到的记录是一个字节串,将其先解码,得到"我",再使用客户端的字符集规则进行编码,得到新的字字节串"0xE68891" ,然后发送给客户端
(5)客户端得到了"0xE68891",于是可以顺利解释成"我"然后呈现给人类
总结:
请求的字符 -》 对应的字节串 -〉 中途网络运输的01串 -》 服务器端 -〉 按照规则还原为对应的字节串 -》 再解码得到字符 -〉按照服务器的变量进行编码得到相应的字节串 -》 根据这个字节串进行查找 -〉 得到字节串 -》 再解码这个字节串得到字符 -〉 依据客户端编码为新的字节串 -》 发送回客户端
其实就是不停的转换翻译的过程,主要是客户端的编码 -〉 服务器的编码 - 》 客户端的编码。中途是根据字符进行编码的
注意:客户端使用的字符集要和服务器系统变量中设置的一致,否则会出现错误
set_connection使用的字符集一定要包含请求中的字符,否则导致无法编码
上面的过程非常麻烦,所以一般将三个系统变量和客户端使用的字符集一致,这样就不用繁琐的转换了
MySQL提供了一条很方便的语句:
SET NAMES 字符集名;
执行效果和分别设置三个系统参数的效果是一样的:
SET character_set_client = xxx;
SET character_set_connection = xxx;
SET character_set_results = xxx;
注意:如果使用Mac系统一般都是utf8,使用win的话默认是gbk
如果启动客户端的时候就想这样设置,可以指定一个default-character-set的启动项:
配置文件:
client
default-character-set=utf8
执行效果和SET NAMES utf8是一样的
比较规则的运用
比较规则通常运用于比较字符串的大小以及某个字符串排列的排序
有时候比较规则也叫做排序规则
总结:
(1)字符集:某些字符 + 这些字符的编码规则
(2)比较规则: 针对某个字符集的比较大小的一种规则
(3)MySQL中,一个字符集可以有多个比较规则,一个字符集有一个默认的比较规则,一个比较规则必须对应一个字符集
(4)查看MySQL中支持的字符集和比较规则:
SHOW (CHARACTER SET | CHARSET) 【LIKE 匹配的模式】
SHOW COLLATION 【LIKE 匹配的模式】
(5)MySQL有四个级别的字符集和比较规则
服务器 -》 数据库 -〉 表 -》列
(6)发送请求到接收结果过程中发生的字符集的转换
客户端 -〉服务器接收到按照客户端的模式解码得到原字符 -》 由这个原字符再按照服务器端的模式编码得到对应字节码 -〉 由这个字节码查询到数据 -》 再逆过程将数据返回给客户端
(7)比较规则用于比较字符串的大小的表达式以及某个字符串排列的方式
个人思考:还是一些基础概念性的东西比较多,理解记忆即可