從鍵盤到數據庫,我的數據是如何“面目全非的”!

# 一、概念介紹

## 字符集

在計算機中只能存儲二進制數據,那該怎麼存儲字符串呢?當然是建立字符與二進制數據的映射關係,分兩步:

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


分享到:


相關文章: