从键盘到数据库,我的数据是如何“面目全非的”!

# 一、概念介绍

## 字符集

在计算机中只能存储二进制数据,那该怎么存储字符串呢?当然是建立字符与二进制数据的映射关系,分两步:

1、哪些字符映射成二进制数据?

2、怎么映射?

例子:

比方说我们来自定义一个名称为xiaohaizi的字符集,它包含的字符范围和编码规则如下:

1. 包含字符'a'、'b'、'A'、'B'。

2. 采用1个字节编码一个字符的形式,字符和字节的映射关系(字符集)如下:

'a' -> 00000001 (十六进制:0x01)

'b' -> 00000010 (十六进制:0x02)

'A' -> 00000011 (十六进制:0x03)

'B' -> 00000100 (十六进制:0x04)

根据上边的字符集来翻译一下:“bA”,如下:

'bA' -> 0000001000000011 (十六进制:0x0203)

## 比较规则

怎么比较两个字符的大小呢?

1. 二进制比较规则

顾名思义就是转化成字符转二进制后比较,如在英文场景不区分大小写时,这个方法就失效,更别说对于汉字的比较了。

注:个人理解比较规则是基于B+数的结构来使用的,在遍历二级索引和聚簇索引中也有应用,感兴趣可以搜一下。

2. 字符集成员

字符集和比较规则属于一对多的关系,先来介绍下字符集的主要成员:

1. ASCII字符集

共收录128个字符,包括空格、标点符号、数字、大小写字母和一些不可见字符(一个字节)。

2. ISO 8859-1字符集(拉丁)

共收录256个字符,是在ASCII字符集的基础上又扩充了128个西欧常用字符(也是一个字节)。

3. GB2312字符集

简单来说,就是支持汉字啦,兼容ASSCI。

复杂点说,收录了汉字以及拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母。其中收录汉字6763个,其他文字符号682个。(两个字节)

有用的提示:

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

4. GBK字符集

扩大表示范围而已。

5. utf8字符集(又称utf8mb3)

收录地球上能想到的所有字符。(一到三字节)

有用的提示:

utf8只是Unicode字符集的一种编码方案;

Unicode字符集可以采用utf8、utf16、utf32这几种编码方案;

例子:

对于汉字'我'来说,ASCCI表示不了,0-127范围不够;gb2312、gbk、utf这些可以,就是占的地方大小不一样。

6. utf8mb4字符集

简单来说,utf8的升级,占地方。

复杂来说,在MySQL中utf8是utf8mb3的别名,所以之后在MySQL中提到utf8就意味着使用1~3个字节来表示一个字符,如果大家有使用4字节编码一个字符的情况,比如存储一些emoji表情啥的,那请使用utf8mb4。

# 二、应用篇

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

1. 服务器级别

2. 数据库级别

3. 表级别

4. 列级别

注:个人理解这些都是字面意思,只是如果定义数据库、表、或者列的时候不特意指定字符集和比较规则的话,会默认使用上一级的配置。

1、查看字符集和比较规则的命令如下(感兴趣可以看看):

SHOW VARIABLES LIKE '下边常量'

服务器:character_set_server/collation_server

数据库:character_set_database/collation_database(好像只读,可以自己试试)

表:show create table具体看下

列:不指定,默认-》表规则(也没指定)-》库规则(也没。。。)-》服务器规则

2、改字符集和比较规则=》用alter/modify。

注:单独修改字符集或者比较规则,会重置比较规则或者字符集(它们是配对的)。

# 三、通信中的字符集

先来一个例子:

UTF-8编码的“我”字,在utf8字符集编码下的字节串长这样:0xE68891

## 》》》开始解码《《《

### GBK2312怎么干,步骤如下:

1. 首先看第一个字节0xE6,它的值大于0x7F(十进制:127),说明是两字节编码,继续读一字节后是0xE688,然后从gbk编码表中查找字节为0xE688对应的字符,发现是字符'鎴'

2. 继续读一个字节0x91,它的值也大于0x7F,再往后读一个字节发现木有了,所以这是半个字符。

3. 所以0xE68891被gbk字符集解释成一个字符'鎴'和半个字符。

### iso-8859-1怎么干,步骤如下:

1. 先读第一个字节0xE6,它对应的latin1字符为æ。

2. 再读第二个字节0x88,它对应的latin1字符为ˆ。

3. 再读第三个字节0x91,它对应的latin1字符为‘。

4. 所以整串字节0xE68891被latin1字符集解释后的字符串就是'我'

## 如果一个字符在传送过程中,可以转换,就可以解决这个问题啦。

字符集转换:

简单来说,字符集在传输过程中是可以通过几个变量来控制的,SHOW VARIABLES LIKE一下:

1、character_set_client服务器解码请求时使用的字符集

2、character_set_connection3、服务器处理请求时会把请求字符串从character_set_client转为character_set_connection

character_set_results服务器向客户端返回数据时使用的字符集

复杂来说,从客户端发往服务器的请求本质上就是一个字符串,服务器向客户端返回的结果本质上也是一个字符串,而字符串其实是使用某种字符集编码的二进制数据。这个字符串可不是使用一种字符集的编码方式一条道走到黑的,从发送请求到返回结果这个过程中伴随着多次字符集的转换。

一个例子:

1、客户端发送请求所使用的字符集。

类Unix系统使用的是utf8,Windows使用的是gbk(可以验证下)。

2、服务器接收到并认为这串字节采用的字符集是character_set_client,然后把这串字节转换为character_set_connection字符集编码的字符。

注:character_set_client这个地方字符集如果不大于等于客户端发过来的字符串用的那个,后果应该是一片“花海”。

3、列也是可以指定字符集的,之前说过,如果这个地方和character_set_connection兼容了,查改都不是事。

4、最后,character_set_results这个是相应回去的字符集,高于请求时字符串编码使用的字符集,应该也是不认识的。

注:个人理解,如果从客户端到服务器都用一种编码,肯定没问题,不然的话,起码要控制好character_set_client和character_set_results这两个进出口。

从键盘到数据库,我的数据是如何“面目全非的”!

软件的概念: 数字背景 Mysql


分享到:


相關文章: