良好的邏輯設計和物理設計是高性能的基石,應該根據系統將要執行的查詢語句來設計schema,這每每須要權衡各類因素。例如,反範式的設計能夠加快某些類型的查詢,但同時可能使另外一些類型的查詢變慢。好比添加計數表和彙總表是一種很好的優化查詢的方式,但這些表的維護成本可能會很高。MySQL獨有的特性和實現細節對性能的影響也很大。數據庫
MySQL支持的數據類型很是多,選擇正確的數據類型對於得到高性能相當重要。無論存儲哪一種類型的數據,下面幾個簡單的原則都有助於作出更好的選擇。緩存
通常狀況下,應該儘可能使用能夠正確存儲數據的最小數據類型。更小的數據類型一般更快,由於它們佔用更少的磁盤、內存和CPU緩存,而且處理時須要的CPU週期也更少。服務器
簡單數據類型的操做一般須要更少的CPU週期。例如,整型比字符操做代價更低,由於字符集和校對規則(排序規則)使字符比較比整型比較更復雜。這裏有兩個例子:一個是應該使用MySQL內建的類型而不是字符串來存儲日期和時間,另一個是應該用整型存儲IP地址。數據結構
若是查詢中包含可爲NULL的列,對MySQL來講更難優化,不使用NULL的理由有:併發
在爲列選擇數據類型時,第一步須要肯定合適的大類型:數字、字符串、時間等,下一步是選擇具體類型。不少MySQL的數據類型能夠存儲相同類型的數據,只是存儲的長度和範圍不同、容許的精度不一樣,或者須要的物理空間(磁盤和內存空間)不一樣。相同大類型的不一樣子類型數據有時也有一些特殊的行爲和屬性。函數
若是存儲整數,可使用這幾種整數類型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。分別使用8,16,24,32,64位存儲空間。它們能夠存儲的值的範圍從−2(N−1)到2(N−1)−1,其中N是存儲空間的位數。性能
整數類型有可選的UNSIGNED屬性,表示不容許負值,這大體可使正數的上限提升一倍。例如TINYINT UNSIGNED能夠存儲的範圍是0~255,而TINYINT的存儲範圍是−128~127。有符號和無符號類型使用相同的存儲空間,並具備相同的性能,所以能夠根據實際狀況選擇合適的類型。優化
注:IP地址其實是32位無符號整數,應該用INT存儲,MySQL提供INETATON和INETNTOA兩個轉換IP地址的函數。網站
實數是帶有小數部分的數字。然而,它們不僅是爲了存儲小數部分,也可使用DECIMAL存儲比BIGINT還大的整數。MySQL既支持精確類型,也支持不精確類型。FLOAT和DOUBLE類型支持使用標準的浮點運算進行近似計算,若是須要知道浮點運算是怎麼計算的,則須要研究所使用的平臺的浮點數的具體實現。DECIMAL類型用於存儲精確的小數,在MySQL 5.0和更高版本,DECIMAL類型支持精確計算。編碼
浮點和DECIMAL類型均可以指定精度。對於DECIMAL列,能夠指定小數點先後所容許的最大位數,這會影響列的空間消耗,MySQL 5.0和更高版本將數字打包保存到一個二進制字符串中(每4個字節存9個數字)。例如,DECIMAL(18,9)小數點兩邊將各存儲9個數字,一共使用9個字節:小數點前的數字用4個字節,小數點後的數字用4個字節,小數點自己佔1個字節。
浮點類型在存儲一樣範圍的值時,一般比DECIMAL使用更少的空間。FLOAT使用4個字節存儲。DOUBLE佔用8個字節,相比FLOAT有更高的精度和更大的範圍。
由於須要額外的空間和計算開銷,因此應該儘可能只在對小數進行精確計算時才使用DECIMAL——例如存儲財務數據。但在數據量比較大的時候,能夠考慮使用BIGINT代替DECIMAL,將須要存儲的貨幣單位根據小數的位數乘以相應的倍數便可,這樣能夠同時避免浮點存儲計算不精確和DECIMAL精確計算代價高的問題。
因爲如今基本上全部的MySQL數據庫使用的都是InnoDB存儲引擎,並且使用的字符集都是utf8或者utf8mb4這樣的多字節字符集。在這種狀況下,varchar和char類型都須要使用1~2個額外字節去存儲字符串的長度,此時char相比varchar已經不具備任何的優點,因此推薦全部的字符串類型都使用varchar。
BLOB和TEXT都是爲存儲很大的數據而設計的字符串數據類型,分別採用二進制和字符方式存儲。
MySQL對BLOB和TEXT列進行排序與其餘類型是不一樣的:它只對每一個列的最前max_sort_length字節而不是整個字符串作排序。若是隻須要排序前面一小部分字符,則能夠減少max_sort_length的配置,或者使用ORDER BY SUSTRING(column,length)。
MySQL不能將BLOB和TEXT列所有長度的字符串進行索引,也不能使用這些索引消除排序。同時由於Memory引擎不支持BLOB和TEXT類型,因此,若是查詢使用了BLOB或TEXT列而且須要使用隱式臨時表,將不得不使用磁盤臨時表。
最好的解決方案是儘可能避免使用BLOB和TEXT類型。若是實在沒法避免,有一個技巧是在全部用到BLOB字段的地方都使用SUBSTRING(column,length)將列值轉換爲字符串(在ORDER BY子句中也適用),這樣就可使用內存臨時表了。可是要確保截取的子字符串足夠短,不會使臨時表的大小超過max_heap_table_size或tmp_table_size,超過之後MySQL會將內存臨時錶轉換爲MyISAM磁盤臨時表。
DATETIME類型能保存1001年到9999年範圍的值,精度爲秒,與時區無關,使用8個字節的存儲空間。TIMESTAAMP類型保存了格林尼治標準時間以來的秒數,只能表示1970年到2038年範圍的值,顯示的值依賴時區,使用4個字節的存儲空間。
一般應該儘可能使用TIMESTAMP,由於它比DATETIME空間效率更高。
注:MySQL5.6.4版本開始支持比秒更小的存儲粒度,格式爲 時間類型(如timestamp)(n),n最大爲6。
整數一般是標識列最好的選擇,由於它們很快而且可使用AUTO_INCREMENT。
若是可能,應該避免使用字符串類型做爲標識列,例如MD5()、SHA1()或者UUID()產生的字符串。由於它們很消耗空間,而且一般比數字類型慢。這些函數生成的新值會任意分佈在很大的空間內,這會致使INSERT以及一些SELECT語句變得很慢:
MySQL的存儲引擎API工做時須要在服務器層和存儲引擎層之間經過行緩衝格式拷貝數據,而後在服務器層將緩衝內容解碼成各個列。從行緩衝中將編碼過的列轉換成行數據結構的操做代價是很是高的,轉換的代價依賴於列的數量。
若是查詢中存在過多的關聯,那麼解析和優化查詢的代價會成爲MySQL的問題。一個粗略的經驗法則,若是但願查詢執行得快速且併發性好,單個查詢最好在12個表之內作關聯。
若是列的值可能會在之後擴充,那麼就應該避免使用ENUM類型。在MySQL 5.1和更新版本中,若是不是在列表的末尾增長值會須要ALTER TABLE,對於大表來講會致使嚴重的性能問題。
範式的優勢:
範式的缺點:
範式化和反範式化的schema各有優劣,怎麼選擇最佳的設計?事實是,在實際應用中常常須要混用,可能使用部分範式化的schema、緩存表,以及其餘技巧。最多見的反範式化數據的方法是複製或者緩存,在不一樣的表中存儲相同的特定列。
在某些須要特定的查詢條件和排序的狀況下,能夠在父表中冗餘一些字段到子表。例若有user表和message表,要查詢付費用戶最近10條數據,徹底範式化查詢的效率較低下,能夠在message表中冗餘帳戶類型的字段並創建好索引,這將很是高效。不過更新帳戶類型的時候須要更新兩張表。這時須要考慮更新的頻率及時長,來和查詢的頻率做比較,而後作出取捨。
緩存衍生值也是有用的。若是須要顯示每一個用戶發了多少消息(像不少論壇作的),能夠每次執行一個昂貴的子查詢來計算並顯示它,也能夠在user表中建一個num_messages列,每當用戶發新消息時更新這個值。
延伸閱讀:
對關係型數據庫五個範式的理解
如何理解關係型數據庫的常見設計範式?
有時提高性能最好的方法是在同一張表中保存衍生的冗餘數據。然而,有時也須要建立一張徹底獨立的彙總表或緩存表(特別是爲知足檢索的需求時)。若是能允許少許的髒數據,這是很是好的方法,可是有時確實沒有選擇的餘地(例如,須要避免複雜、昂貴的實時更新操做)。
術語「緩存表」和「彙總表」沒有標準的含義。咱們用術語「緩存表」來表示存儲那些能夠比較簡單地從schema其餘表獲取(可是每次獲取的速度比較慢)數據的表(例如,邏輯上冗餘的數據)。而術語「彙總表」時,則保存的是使用GROUP BY語句聚合數據的表(例如,數據不是邏輯上冗餘的)。也有人使用術語「累積表(Roll-Up Table)」稱呼這些表。由於這些數據被「累積」了。
以網站爲例,假設須要計算以前24小時內發送的消息數。在一個很繁忙的網站不可能維護一個實時精確的計數器。做爲替代方案,能夠每小時生成一張彙總表。這樣也許一條簡單的查詢就能夠作到,而且比實時維護計數器要高效得多。缺點是計數器並非100%精確。
若是必須得到過去24小時準確的消息發送數量(沒有遺漏),有另一種選擇。以每小時彙總表爲基礎,把前23個完整的小時的統計表中的計數所有加起來,最後再加上開始階段和結束階段不完整的小時內的計數。
固然,更好的方法是使用內存數據庫來完成這個計數器,例如Redis。