接著上一篇 MySQL 資料庫效能最佳化之緩衝參數最佳化 ,這是 MySQL資料庫效能最佳化專題 系列的第二篇文章:MySQL 資料庫效能最佳化之表結構
很多人都將 資料庫設計範式 作為資料庫表結構設計“聖經”,認為只要按照這個範式需求設計,就能讓設計出來的表結構足夠最佳化,既能保證效能優異同時還能滿足擴充性要求。殊不知,在N年前被奉為“聖經”的資料庫設計3範式早就已經不完全適用了。這裡我整理了一些比較常見的資料庫表結構設計方面的最佳化技巧,希望對大家有用。
由於MySQL資料庫是基於行(Row)儲存的資料庫,而資料庫操作 IO 的時候是以 page(block)的方式,也就是說,如果我們每條記錄所佔用的空間量減小,就會使每個page中可存放的資料行數增大,那麼每次 IO 可訪問的行數也就增多了。反過來說,處理相同行數的資料,需要訪問的 page 就會減少,也就是 IO 操作次數降低,直接提升效能。此外,由於我們的記憶體是有限的,增加每個page中存放的資料行數,就等於增加每個記憶體塊的快取資料量,同時還會提升記憶體換中資料命中的幾率,也就是快取命中率。
- 資料類型選擇
資料庫操作中最為耗時的操作就是 IO 處理,大部分資料庫操作 90% 以上的時間都花在了 IO 讀寫上面。所以儘可能減少 IO 讀寫量,可以在很大程度上提高資料庫操作的效能。我們無法改變資料庫中需要儲存的資料,但是我們可以在這些資料的儲存方式方面花一些心思。下面的這些關於欄位類型的最佳化建議主要適用於記錄條數較多,資料量較大的情境,因為精細化的資料類型設定可能帶來維護成本的提高,過度最佳化也可能會帶來其他的問題:
- 數字類型:非萬不得已不要使用DOUBLE,不僅僅只是儲存長度的問題,同時還會存在精確性的問題。同樣,固定精度的小數,也不建議使用DECIMAL,建議乘以固定倍數轉換成整數儲存,可以大大節省儲存空間,且不會帶來任何附加維護成本。對於整數的儲存,在資料量較大的情況下,建議區分開 TINYINT / INT / BIGINT 的選擇,因為三者所佔用的儲存空間也有很大的差別,能確定不會使用負數的欄位,建議添加unsigned定義。當然,如果資料量較小的資料庫,也可以不用嚴格區分三個整數類型。
- 字元類型:非萬不得已不要使用 TEXT 資料類型,其處理方式決定了他的效能要低於char或者是varchar類型的處理。定長欄位,建議使用 CHAR 類型,不定長欄位盡量使用 VARCHAR,且僅僅設定適當的最大長度,而不是非常隨意的給一個很大的最大長度限定,因為不同的長度範圍,MySQL也會有不一樣的儲存處理。
- 時間類型:盡量使用TIMESTAMP類型,因為其儲存空間只需要 DATETIME 類型的一半。對於只需要精確到某一天的資料類型,建議使用DATE類型,因為他的儲存空間只需要3個位元組,比TIMESTAMP還少。不建議通過INT類型類儲存一個unix timestamp 的值,因為這太不直觀,會給維護帶來不必要的麻煩,同時還不會帶來任何好處。
- ENUM & SET:對於狀態欄位,可以嘗試使用 ENUM 來存放,因為可以極大的降低儲存空間,而且即使需要增加新的類型,只要增加於末尾,修改結構也不需要重建表資料。如果是存放可預先定義的屬性資料呢?可以嘗試使用SET類型,即使存在多種屬性,同樣可以遊刃有餘,同時還可以節省不小的儲存空間。
- LOB類型:強烈反對在資料庫中存放 LOB 類型資料,雖然資料庫提供了這樣的功能,但這不是他所擅長的,我們更應該讓合適的工具做他擅長的事情,才能將其發揮到極致。在資料庫中儲存 LOB 資料就像讓一個多年前在學校學過一點Java的營銷專業人員來寫 Java 代碼一樣。
- 字元編碼
字元集直接決定了資料在MySQL中的儲存編碼方式,由於同樣的內容使用不同字元集表示所佔用的空間大小會有較大的差異,所以通過使用合適的字元集,可以協助我們儘可能減少資料量,進而減少IO操作次數。
- 純拉丁字元能表示的內容,沒必要選擇 latin1 之外的其他字元編碼,因為這會節省大量的儲存空間
- 如果我們可以確定不需要存放多種語言,就沒必要非得使用UTF8或者其他UNICODE字元類型,這回造成大量的儲存空間浪費
- MySQL的資料類型可以精確到欄位,所以當我們需要大型資料庫中存放多位元組資料的時候,可以通過對不同表不同欄位使用不同的資料類型來較大程度減小資料存放區量,進而降低 IO 操作次數並提高快取命中率
- 適當拆分
有些時候,我們可能會希望將一個完整的對象對應於一張資料庫表,這對於應用程式開發來說是很有好的,但是有些時候可能會在效能上帶來較大的問題。當我們的表中存在類似於 TEXT 或者是很大的 VARCHAR類型的大欄位的時候,如果我們大部分訪問這張表的時候都不需要這個欄位,我們就該義無反顧的將其拆分到另外的獨立表中,以減少常用資料所佔用的儲存空間。這樣做的一個明顯好處就是每個資料區塊中可以儲存的資料條數可以大大增加,既減少物理 IO 次數,也能大大提高記憶體中的快取命中率。
上面幾點的最佳化都是為了減少每條記錄的儲存空間大小,讓每個資料庫中能夠儲存更多的記錄條數,以達到減少 IO 操作次數,提高快取命中率。下面這個最佳化建議可能很多開發人員都會覺得不太理解,因為這是典型的反範式設計,而且也和上面的幾點最佳化建議的目標相違背。