高性能MySql進化論(一):數據類型的優化_上
在數據庫的性能調優的過程中會涉及到很多的知識,包括字段的屬性設置是否合適,索引的建立是否恰當,表結構涉及是否合理,數據庫/操作系統 的設置是否正確…..其中每個topic可能都是一個領域。
在我看來,在數據庫性能提升關鍵技術中,對字段的優化難度相對較低且對性能的影響也非常的大。由於Mysql支持的數據類型比較多,且每個類型都有其獨特的特性,但是有時候在選擇一個具體的數據類型時,往往都是隨意的選擇一個能用的類型,而不會考慮到這個類型是否是最優的。在具體的類型描述之前,先來看一些針對數據類型選擇的主要原則:
a) 盡量選擇占用空間小的類型
因為小的類型無論是在磁盤,還是在內存中占用的空間都是小的,在進行查詢或者排序是臨時表要求的空間也會相對較少。在數據量比較小的時候可能感覺不到,但是當數據量比較大時,這個原則的重要性可能就會得到顯現。
例如,有一張“商品信息”表,記錄為2000萬條,這張表有個 “剩余商品數量”(COUNT)的字段,一般而言 SMALLINT (len:16 range:0-65535)已經足夠表達這個字段,可是如果你在設計的過程中用了BIGINT(len:64 range:0-18446744073709551615)來表達,雖然說程序可能正確的運行,但是這一個字段將會額外的增加大概95M的磁盤存儲空間(64-16)/8*20,000,000 Bytes),另外在做數據選擇和排序時僅僅這一個字段就會增加你95M的內存消耗,基於以上行為的影響,數據庫的Performance必然是會被影響的
這裡說的盡量小的前提是確保你將要選擇的類型可以滿足日後業務發展的需求,因為在數據量比較大的時候做表結構的更新是個非常緩慢而且麻煩的事情。
b) 盡量選擇簡單/恰當的類型
在對表進行選擇以及排序的時候,對於簡單的類型往往只需要消耗較少的CPU時鐘周期。例如,對於MySql server而言,整數類型值的Compare往往會比字符串類型值的Compare簡單且快,所以當你需要對特定的表進行排序時應該盡量選擇整數類型作為排序的依據
c) 盡量將字段設置為NOTNULL
一般情況下,如果你沒有顯示的制定一個字段為NULL,那麼這個字段將會被數據庫系統認為是NULLABLE, 系統的這種默認行為將會導致以下三個問題
(1) Mysql服務器自身的 查詢優化功能將會受影響
(2) Mysql針對null值的字段需要額外的存儲空間以及處理
(3) 如果一個null值是索引的一部分,那麼索引的效果也會收到影響
由於這個原則對於數據庫性能提升的作用不是很大,所以對於已經存在的DB schema,其存在NULLABLE字段或者是索引為NULLABLE的,也不用專門的去修改它,但是對於新設計的DB或者索引需要盡量遵守這個原則。
介紹完了數據類型選擇的原則後,接下來將會介紹Mysql中常見的數據類型以及在性能優化方面需要注意的地方。
· 整數
在Mysql 的整數家族成員中主要包括TINYINT(8bit), SMALLINT(16bit), MEDIUMINT(24bit), INT(32bit), or BIGINT(64bit)。
對於有符號整數而言這些類型的存儲范圍為(-2(n-1) ,2(n-1)-1),對於無符號數而言表達的范圍是(0,2n-1),對於數據庫而言有符號數和無符號數占用相同的存儲空間,所以在選擇類型的時候可以只考慮數的區間,而不用考慮是signed還是unsigned
Mysql允許你在定義整數類型時指定他的寬度,例如 INT(10)。INT(10) 對於Client/CMD Line的輸出是有區別的,但在Mysql Server看來實際的存儲空間/計算消耗/數字范圍 INT(10)與INT(32)沒有任何的區別。
· 小數
在Mysql中小數家族的數據類型主要包括FLOAT(4Bytes),DOUBLE(8Bytes),從這兩種類型的存儲空間可以看出小數的存取比整數需要消耗更多的空間,所以除非必須,否則應該盡量避免使用小數的類型
創建小數類型的字段時,你可以使用FLOAT(10,3)的方式來指定小數的精度,>=Mysql 5.0的版本中最大的精度支持到小數點後65位。
由於數據庫采用Binary Array String的方式來存儲小數點後面的數字,所以你要求的精度越高,存儲空間/計算的CPU時鐘可能消耗的也就越高。
雖然使用小數可能會消耗更多的存儲空間以及CPU資源,而且對於早期的Mysql版本還會出現當兩個小數參與計算時精度丟失的情況,但是在很多情況下它又是必須的,例如在金融領域中關於金額的存儲。在很多情況下為了減少存儲的開銷以及保證精度的准確性,往往會把小數擴大至整數存儲在數據庫中,而在Application中再進行小數的轉換以及計算,例如 某個用戶的賬戶余額還剩下999.35元,那麼在數據中存儲的金額為99935分,銀行的處理程序拿到99935分後會先轉換成999.35元,然後再進行相應的處理
· 字符串
不管對於哪門語言而言,字符串都是一個比較重要且復雜的類型,這個規律對於MYSQL同樣適用
在MYSQL中主要包括VARCHAR以及CHAR兩種字符串類型,對於這兩種字符串類型在磁盤以及內存中存儲方式是由Storage engine決定的,且不同的storage engine可能會有不同的存儲方式。一般情況下對於一種storage engine 而言,在磁盤以及內存中的存儲方式也是不同的,當數據在磁盤與內存之間轉移時,storage engine將會負責把數據進行轉換
VARCHAR
首先需要指出的是Mysql是用variable length的方式來來存儲VARCHAR,相對於fixed length,這種方式對存儲空間采取的策略是“用多少,要多少”,是一種比較節省空間的存儲方案,在沒有特殊需求的情況下可以作為默認的類型
VARCHAR之所以可以實現定長,是因為每個VARCHAR值都會附加一個 長度為1-2byte 的長度指示器,例如當需要存儲“I Love Java”時,底層的存儲內容為 “11I Love Java”,其中11(1 Byte)代表長度。當需要存儲內容的長度為1000時長度指示器就需要兩個字節。因為2bytes的最大值為216,所以當存儲的字符串超過這個長度時,會出現不可預料的異常,這時就需要使用CLOB來存儲這種超長的字符串。
在MYSQL的不同版本中,針對VARCHAR字段的結尾空格處理也有所不同
Version>=5.0 保留結尾的空格
Version<=4.1 截取空格
以MYSQL 5.6 為例:
▪ 使用VARCHAR(5) 和VARCHAR(200) 存儲’hello’的空間開銷是一樣的。那麼使用更短的列有什麼優勢嗎?
事實證明有很大的優勢。更大的列會消耗更多的內存,因為MySQL 通常會分配固定大小的內存塊來保存內部值。尤其是使用內存臨時表進行排序或操作時會特別糟糕。在利用磁盤臨時表進行排序時也同樣糟糕。
所以最好的策略是只分配真正需要的空間。
CHAR
CHAR類型與VARCHAR類型最大的區別在於它是定長的。同時相比於VARCHAR它主要有以下特點
1)在所有的MYSQL版本中,末尾的空格都會被截取
2)對於 一些短的且是長度基本相同的字段是個不錯的選擇例如MD5,ID Number
3)對於經常需要變更的字段,CHAR類型會更高效
4)對於一些超短的字段,也非常的節約空間。例如你保存“Y”或者是“N”,用CHAR只需要一個字節,而用VARCHAR 的話需要兩個字節(1byte length+1 byte value)
對於定長的CHAR,Mysql server會根據其定義的長度采用補空格的方式來分配足夠大的存儲空間。有一點需要注意的是 VARCHAR/CHAR在進行“補空格”以及“去結尾空格”的操作是由Mysql server來實現的,與Storage engine 無關