從 Mysql 數據庫角度來講,談到存儲就必定離不開字符集,只不過在咱們平常開發中統一的 utf8/utf8mb4 編碼,使咱們經常忽略了字符集的影響,本文僅從字符集的角度來談談對 InnoDB 的存儲設計的一點影響,以及 Mysql 是怎麼兼容各類字符集的。sql
Unicode 做爲如今通用的字符集,一般採用兩個字節表示一個字符,帶來的反作用就是,本來採用 ASCII 字符集只須要一個字節的,卻變成了 2 個字節,形成了空間浪費,而 UTF-8 編碼規則,將 Unicode 編碼成 1~4 個字節,ASCII 字符集繼續保持了 1 個字節空間,而中文編碼成了三個字節,以下圖。數據庫
先說明下 Mysql 中存在兩種字符集 utf8 和 utf8mb4,如下例子均以 Mysql 的 utf8(1~3個字節)爲例。性能優化
採用 utf8 編碼的確很不錯,可是也帶來了一個問題,例如我在 Mysql 中定義了一個定長字符類型 char(5):服務器
name | type | length |
---|---|---|
title | char | 5 |
所謂定長字符類型表明我要給 title 分配 5 個字符大小的空間,但是 utf8 每一個字符多是 1~3 個字節,我該分配多少空間合適呢?ide
理論上爲了兼容,最好應該採用 utf8 的最大 3 個字節進行分配,也就是 5*3 = 15 個字節的空間,這樣我無論之後怎麼修改這個字段的值,空間都能完美知足需求,可是若是此時存儲的都是英文,好比 5 個 I,就會足足浪費 10 個字節的空間,若是這列之後都存英文,那麼至少會浪費 2/3 的空間。性能
在 Mysql5.0 以前的行格式設計中,也就是 Redundant 行格式,char(5) 的確就如上面設計佔用了 15 個字節空間,隨着版本的迭代,後續出來的 Compact,Dynamic,Compressed 行格式都採用了另外一種設計。優化
在對於 utf8 這類變長編碼規則的 char 類型,採用同 varchar 類型同樣的存儲方式,就是在前面用一個或兩個字節表示該列實際佔用的字節數,對應到上圖存儲 5 個 I 的例子,就是 05(實際佔用字節數)+5 個存儲 I 的字節空間。編碼
固然,更極端點,我只存儲了一個 I,這時 char(5) 就會使用 utf8 的最小字節數 1*5(char定義的字符長度)的大小做爲最小分配空間,空出的 4 個字節空間用空格字符填充,也就是說,對於 title 來講,至少會分配 5 個字節空間。設計
上面只是對列爲 char(5) 的數據進行說明,在真實數據庫表中,會存在多列 varchar 或 char 類型,由上可知變長編碼規則的 char 也是按 varchar 處理的,因此這些列的實際佔用字節數都會逆序存放在行格式首部,被稱爲變長字段長度列表,而每列的數據,則是順序存放在列值中,以下圖,至於變長字段長度列表和 Null 值列表爲何是逆序的,你們有興趣能夠去想一想。3d
採用上面的設計,在大部分狀況下能省下了不少空間,也提高了查詢效率,可是也帶來了另外一個問題,那就是更新,用兩個例子說明下:
將 title 從 1個 I 更新爲 5 個 I
這個很好處理,由於 char(5) 最低會分配 5 個字節空間,更改成 5 個 I,不會產生任何影響,直接更新就好。
將 title 從 5 個 I 更改成 5 個我
5 個我 = 5 * 3 = 15 個字節空間,而實際記錄只有 5 個字節空間,空間不足以支撐更新,這時候的更新只能將原數據的整行記錄刪除,而後再新分配合適空間供其使用,看似也沒什麼,可是這種刪 + 增實際會對頁產生不少變動,這麼多變動又要保證它的事務性,也就是記錄 redo , undo 日誌,仍是挺複雜和麻煩的。
一個請求從客戶端到 Mysql 服務器,再到表,再返回給客戶端,中間是通過多層字符集轉換的,主要包括下面4層:
轉換配置 | 說明 | 例子 |
---|---|---|
character_set_client | 客戶端請求所用字符集 | utf8 |
character_set_connection | 服務器將 character_set_client 轉碼爲 character_set_connection | gbk |
表、列字符集 | 將 character_set_connection 轉碼爲表、列定義的字符集,反之亦然 | ascii |
character_set_results | 返回客戶端字符集 | utf8 |
假設咱們查詢 title 列,而且 Mysql 各類變量以及列字符集採用上面表格的例子,那麼轉換以下:
select title from title_demo where title = 'IIIIII'
固然,實際開發中,咱們都會統一均採用 utf8 ,這樣就有效避免了各層字符集轉換帶來的性能影響。
隨着 Mysql 性能的提高,其代碼實現複雜度也顯著提高,爲了性能,對一種場景進行區分各類狀況,再對各類狀況進行不一樣的優化處理,已經再也不陌生,因此面對這麼多複雜的實現,從一個小的切入點,發現其樂趣,也是挺有意思的。