談談性能優化:Mysql 的字符集以及帶來的一點存儲影響

前言

從 Mysql 數據庫角度來說,談到存儲就一定離不開字符集,只不過在我們日常開發中統一的 utf8/utf8mb4 編碼,使我們常常忽略了字符集的影響,本文僅從字符集的角度來談談對 InnoDB 的存儲設計的一點影響,以及 Mysql 是怎麼兼容各種字符集的。

過一下字符集

Unicode 作爲現在通用的字符集,通常採用兩個字節表示一個字符,帶來的副作用就是,原本採用 ASCII 字符集只需要一個字節的,卻變成了 2 個字節,造成了空間浪費,而 UTF-8 編碼規則,將 Unicode 編碼成 1~4 個字節,ASCII 字符集繼續保持了 1 個字節空間,而中文編碼成了三個字節,如下圖。

談談性能優化:Mysql 的字符集以及帶來的一點存儲影響

對存儲帶來了什麼影響

先說明下 Mysql 中存在兩種字符集 utf8 和 utf8mb4,以下例子均以 Mysql 的 utf8(1~3個字節)爲例。

採用 utf8 編碼的確很不錯,但是也帶來了一個問題,例如我在 Mysql 中定義了一個定長字符類型 char(5):

name type length
title char 5

所謂定長字符類型代表我要給 title 分配 5 個字符大小的空間,可是 utf8 每個字符可能是 1~3 個字節,我該分配多少空間合適呢?

理論上爲了兼容,最好應該採用 utf8 的最大 3 個字節進行分配,也就是 5*3 = 15 個字節的空間,這樣我不管以後怎麼修改這個字段的值,空間都能完美滿足需求,但是如果此時存儲的都是英文,比如 5 個 I,就會足足浪費 10 個字節的空間,如果這列以後都存英文,那麼至少會浪費 2/3 的空間。

談談性能優化:Mysql 的字符集以及帶來的一點存儲影響

在 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 個字節空間。

談談性能優化:Mysql 的字符集以及帶來的一點存儲影響

上面只是對列爲 char(5) 的數據進行說明,在真實數據庫表中,會存在多列 varchar 或 char 類型,由上可知變長編碼規則的 char 也是按 varchar 處理的,所以這些列的實際佔用字節數都會逆序存放在行格式首部,被稱爲變長字段長度列表,而每列的數據,則是順序存放在列值中,如下圖,至於變長字段長度列表和 Null 值列表爲什麼是逆序的,大家有興趣可以去想想。

談談性能優化:Mysql 的字符集以及帶來的一點存儲影響

帶來的更新問題

採用上面的設計,在大部分情況下能省下了很多空間,也提升了查詢效率,但是也帶來了另一個問題,那就是更新,用兩個例子說明下:

將 title 從 1個 I 更新爲 5 個 I

這個很好處理,因爲 char(5) 最低會分配 5 個字節空間,更改爲 5 個 I,不會產生任何影響,直接更新就好。

將 title 從 5 個 I 更改爲 5 個我

5 個我 = 5 * 3 = 15 個字節空間,而實際記錄只有 5 個字節空間,空間不足以支撐更新,這時候的更新只能將原數據的整行記錄刪除,然後再新分配合適空間供其使用,看似也沒什麼,但是這種刪 + 增實際會對頁產生很多變更,這麼多變更又要保證它的事務性,也就是記錄 redo , undo 日誌,還是挺複雜和麻煩的。

Mysql的字符集轉換機制

一個請求從客戶端到 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'

談談性能優化:Mysql 的字符集以及帶來的一點存儲影響

當然,實際開發中,我們都會統一均採用 utf8 ,這樣就有效避免了各層字符集轉換帶來的性能影響。

總結

隨着 Mysql 性能的提升,其代碼實現複雜度也顯著提升,爲了性能,對一種場景進行區分各種情況,再對各種情況進行不同的優化處理,已經不再陌生,所以面對這麼多複雜的實現,從一個小的切入點,發現其樂趣,也是挺有意思的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章