1. 程式人生 > 其它 >MySQL Schema 與資料型別優化

MySQL Schema 與資料型別優化

良好的邏輯設計和物理設計是高效能的基石,應該根據系統將要執行的查詢語句來設計schema,這往往需要權衡各種因素。
例如,反正規化的設計可以加快某些型別的查詢,但同時可能使另一些型別的查詢變慢;新增計數表和彙總表是一種很好的優化查詢的方式,但這些表的維護成本可能會很高。

選擇優化的資料型別

MySQL支援的資料型別非常多,選擇正確的資料型別對於獲得高效能至關重要。基本原則為:

  • 更小的通常更好:一般情況下儘量使用可以正確儲存資料的最小資料型別,更小的資料型別通常更快,因為其佔用更少的磁碟、記憶體和CPU。
  • 簡單就好:簡單資料型別的操作通常需要更小的CPU,如整型比字元的操作代價更低,因為字符集和校對規則(排序)使字元比較比整型比較更復雜。實際例子為:應該使用MySQL內建的型別而不是字串來儲存日期和時間;應該使用整型儲存IP地址。
  • 儘量避免NULL:通常情況下最好指定列為NOT NULL,除非真的需要儲存NULL值,如果計劃在列上建立索引,儘量避免設計成可為NULL的列。

在為列選擇資料型別時,第一步需要確定合適的大型別:數字、字串、時間等。下一步是選擇具體型別。很多MySQL的資料型別可以儲存相同型別的資料,只是儲存的長度和範圍不一樣、允許的精度不同,或者需要的物理空間(磁碟和記憶體空間)不同。相同大型別的不同子型別資料有時也有一些特殊的行為和屬性。
如DATETIME和TIMESTAMP都可以儲存日期時間資料,但是TIMESTAMP使用DATETIME一半的儲存空間,並且具有隨時區變化而自動更新的特性,但其可表示的時間範圍又要小得多。

整數型別
整數型別共有TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。分別使用8,16,24,32,64位儲存空間,儲存範圍從-2(n-1)冪到2(n-1)冪 - 1。整數型別有可選的UNSIGNED屬性,表示不允許負值,這可以使正數的上限提升一倍。MySQL可為整數型別指定寬度,如int(11),但其不會限制值的合法範圍,只是規定了一些互動工具(如MySQL Client)用來顯示字元的個數。對於儲存和計算來說,int(1)、int(11)並無區別。

實數型別
實數是帶有小數部分的數字,但也可以使用DECIMAL儲存比BIGINT還大的整數,MySQL支援精確型別Decimal,也支援不精確型別Float、Double。儲存相同範圍的值,浮點型別通常比Decimal使用更少的空間,計算開銷也更小。

字串型別
VARCHAR型別用於儲存可變長字串,相較之定長型別更節約空間,最多能儲存65535位元組資料。VARCHAR需要使用1或2個額外位元組記錄字串的長度:如果列的最大長度小於或等於255位元組,則只使用1個位元組表示,否則使用2個位元組。但由於其長度是可變的,在Update時可能會使得行變得比原來更長,如果一個行佔用的空間增長,並且在頁內沒有更多的空間儲存時,InnoDB使用分裂頁來使行可以放進頁內。
CHAR型別是定長的,儲存CHAR值時,MYSQL會刪除所有的末尾空格。其適用於儲存很短的字串,如使用CHAR(1)儲存Y/N,若使用VARCHAR(1)來儲存,在相同字符集下會多使用一個位元組來記錄長度;或是接近於同一個長度,如MD5值。對於經常變更的值,char也比varchar更好,其不易產生碎片。

BLOB和TEXT型別
BLOB和TEXT都是為儲存很大的資料而設計的字串資料型別,分別採用二進位制和字元方式儲存,分屬於兩組資料型別家族:TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT,LONGTEXT;對應的二進位制型別是TINYBLOB,SMALLBLOB,BLOB,MEDIUMBLOB,LONGBLOB。
BLOB型別沒有排序規則或字符集,而TEXT型別有字符集和排序規則。BLOB和TEXT在排序時與其他型別也不一樣:其只對每個列的最前max_sort_length位元組而不是整個字元字串進行排序。可配置此屬性或者使用order by substring(column,length)進行部分排序。

使用列舉代替字串型別
列舉列可以把一些不重複的字串儲存成一個預定義的集合,MYSQL儲存列舉時非常緊湊,會根據列表值的數量壓縮到一個或者兩個位元組中。MySQL內部會將每個值在列表中的位置儲存為整數,並且在表的.frm檔案中儲存數字-字串對映關係的查詢表。下面有一個例子:

使用列舉的好處則是可以節約儲存空間,不好的是,字串列表是固定的,新增或者刪除字串必須使用ALTER TABLE,對於一系列未來可能改變的字串,使用列舉不是個好主意。

日期和時間型別
MySQL提供兩種相似的日期型別:DATETIME和TIMESTAMP,精度均為秒,對於大多數應用程式,其都能工作,但某些場景下,一個比另一個工作得更好。
DATETIME:時間範圍為1001~9999年,精度為秒,其將日期時間封裝到YYYYMMDDHHMMSS的整數中,與時區無關,使用8個位元組儲存空間。
TIMESTAMP:儲存了自1970-01-01 00:00:00以來的秒數,使用4個位元組的儲存空間,只能表示1970~2038。TIMESTAMP顯示的值依賴於時區,MySQL伺服器、作業系統,以及客戶端連線都有時區設定。預設情況下,如果插入時沒有指定第一個TIMESTAMP列的值,MYSQL 則會設定這個列的值為當前時間;在更新一行記錄時,也會預設更新第一個TIMESTAMP列的值。也可以配置TIMESTAMP的插入和更新行為,其預設為NOT NULL,其他資料型別則預設為NULL

選擇識別符號
為識別符號選擇合適的資料型別非常重要,在建立索引、聯合查詢時都有很大影響。選定一種型別之後,需要確保在所有關聯的表中都使用同樣的型別,型別之間需要精確匹配,包括UNSIGNED這樣的屬性。混用不同資料型別可能導致效能問題或者由隱式型別轉換帶來額外的問題。

  • 整數型別通常是標識列最好的選擇,速度很快並且可以AUTO_INCREMENT
  • ENUM和SET通常是比較糟糕的選擇,除非是固定狀態資訊
  • 字串型別,也應該選擇其作為識別符號,但佔用更多的空間(索引),通常速度慢於整型,並且其無序插入會增加索引、空間成本。

特殊資料型別
人們經常使用VARCHAR(15)來儲存IPV4地址,然而它們實際上是32位無符號整數,所以應該使用無符號整數來儲存IPV4地址,MySQL提供INET_ATON()和INET_NTOA()函式在字串與無符號整數之間切換。

正規化和反正規化

對於任何給定的資料通常都有很多種表示方法,從完全的正規化化到完全的反正規化化,以及兩者的折中。在正規化化的資料庫中,每個事實資料會出現並且只出現一次,相反,在反正規化化的資料庫中,資訊是冗餘的,可能會儲存在多個地方。正規化化設計的優點:

  • 正規化化的更新操作通常比反正規化化要快,只需要修改更少的資料。
  • 正規化化的表通常更小,可以更好地放在記憶體裡,所以執行操作會更快。
  • 很少有多餘的資料意味著檢索列表資料時更少需要DISTINCT 或者GROUP BY 語句。
    正規化化設計的schema的缺點是通常需要關聯。稍微複雜一些的查詢語句在符合正規化的schema上都可能需要至少一次關聯,也許更多。這不但代價昂貴,也可能使一些索引策略無效。

反正規化的優缺點
反正規化化的schema所有資料都在一張表中,其優點為:

  • 不需要進行表關聯。
  • 單獨的表也能使用更有效的索引策略。
    缺點為更新操作代價過高。實際使用中一般都是正規化化與反正規化化共用。

快取表和彙總表

有時提升效能最好的方法是在同一張表中儲存衍生的冗餘資料。然而,有時也需要建立一張完全獨立的彙總表或快取表(特別是為滿足檢索的需求時)。
我們用術語“快取表”來表示儲存那些可以比較簡單地從schema其他表獲取(但是每次獲取的速度比較慢)資料的表(例如,邏輯上冗餘的資料)。而術語“彙總表”時,則儲存的是使用GROUP BY 語句聚合資料的表(例如,資料不是邏輯上冗餘的)。
以網站為例,假設需要計算之前24小時內傳送的訊息數。在一個很繁忙的網站不可能維護一個實時精確的計數器。作為替代方案,可以每小時生成一張彙總表。這樣一條簡單的查詢就可以做到,並且比實時維護計數器要高效得多。缺點是計數器並不是100%精確。