MySQL資料庫效能優化之表結構優化(轉)

物理狂人發表於2012-05-04

很多人都將作為資料庫表結構設計“聖經”,認為只要按照這個正規化需求設計,就能讓設計出來的表結構足夠優化,既能保證效能優異同時還能滿足擴充套件性要求。殊不知,在N年前被奉為“聖經”的資料庫設計3正規化早就已經不完全適用了。這裡我整理了一些比較常見的資料庫表結構設計方面的優化技巧,希望對大家有用。

        這是 MySQL資料庫效能優化專題 系列的第二篇文章:MySQL 資料庫效能優化之表結構優化

        系列的第一篇文章:MySQL 資料庫效能優化之快取引數優化

  由於MySQL資料庫是基於行(Row)儲存的資料庫,而資料庫操作 IO 的時候是以 page(block)的方式,也就是說,如果我們每條記錄所佔用的空間量減小,就會使每個page中可存放的資料行數增大,那麼每次 IO 可訪問的行數也就增多了。反過來說,處理相同行數的資料,需要訪問的 page 就會減少,也就是 IO 操作次數降低,直接提升效能。此外,由於我們的記憶體是有限的,增加每個page中存放的資料行數,就等於增加每個記憶體塊的快取資料量,同時還會提升記憶體換中資料命中的機率,也就是快取命中率。

  資料型別選擇

  資料庫操作中最為耗時的操作就是 IO 處理,大部分資料庫操作 90% 以上的時間都花在了 IO 讀寫上面。所以儘可能減少 IO 讀寫量,可以在很大程度上提高資料庫操作的效能。

  我們無法改變資料庫中需要儲存的資料,但是我們可以在這些資料的儲存方式方面花一些心思。下面的這些關於欄位型別的優化建議主要適用於記錄條數較多,資料量較大的場景,因為精細化的資料型別設定可能帶來維護成本的提高,過度優化也可能會帶來其他的問題:

  1.數字型別:非萬不得已不要使用DOUBLE,不僅僅只是儲存長度的問題,同時還會存在精確性的問題。同樣,固定精度的小數,也不建議使用DECIMAL,建議乘以固定倍數轉換成整數儲存,可以大大節省儲存空間,且不會帶來任何附加維護成本。對於整數的儲存,在資料量較大的情況下,建議區分開 TINYINT / INT / BIGINT 的選擇,因為三者所佔用的儲存空間也有很大的差別,能確定不會使用負數的欄位,建議新增unsigned定義。當然,如果資料量較小的資料庫,也可以不用嚴格區分三個整數型別。

  2.字元型別:非萬不得已不要使用 TEXT 資料型別,其處理方式決定了他的效能要低於char或者是varchar型別的處理。定長欄位,建議使用 CHAR 型別,不定長欄位儘量使用 VARCHAR,且僅僅設定適當的最大長度,而不是非常隨意的給一個很大的最大長度限定,因為不同的長度範圍,MySQL也會有不一樣的儲存處理。

  3.時間型別:儘量使用TIMESTAMP型別,因為其儲存空間只需要 DATETIME 型別的一半。對於只需要精確到某一天的資料型別,建議使用DATE型別,因為他的儲存空間只需要3個位元組,比TIMESTAMP還少。不建議通過INT型別類儲存一個unix timestamp 的值,因為這太不直觀,會給維護帶來不必要的麻煩,同時還不會帶來任何好處。

  4.ENUM & SET:對於狀態欄位,可以嘗試使用 ENUM 來存放,因為可以極大的降低儲存空間,而且即使需要增加新的型別,只要增加於末尾,修改結構也不需要重建表資料。如果是存放可預先定義的屬性資料呢?可以嘗試使用SET型別,即使存在多種屬性,同樣可以遊刃有餘,同時還可以節省不小的儲存空間。

  5.LOB型別:強烈反對在資料庫中存放 LOB 型別資料,雖然資料庫提供了這樣的功能,但這不是他所擅長的,我們更應該讓合適的工具做他擅長的事情,才能將其發揮到極致。在資料庫中儲存 LOB 資料就像讓一個多年前在學校學過一點Java的營銷專業人員來寫 Java 程式碼一樣。

  字元編碼

  字符集直接決定了資料在MySQL中的儲存編碼方式,由於同樣的內容使用不同字符集表示所佔用的空間大小會有較大的差異,所以通過使用合適的字符集,可以幫助我們儘可能減少資料量,進而減少IO操作次數。

  1.純拉丁字元能表示的內容,沒必要選擇 latin1 之外的其他字元編碼,因為這會節省大量的儲存空間。

  2.如果我們可以確定不需要存放多種語言,就沒必要非得使用UTF8或者其他UNICODE字元型別,這回造成大量的儲存空間浪費。

  3.MySQL的資料型別可以精確到欄位,所以當我們需要大型資料庫中存放多位元組資料的時候,可以通過對不同表不同欄位使用不同的資料型別來較大程度減小資料儲存量,進而降低 IO 操作次數並提高快取命中率。

  適當拆分

  有些時候,我們可能會希望將一個完整的物件對應於一張資料庫表,這對於應用程式開發來說是很有好的,但是有些時候可能會在效能上帶來較大的問題。

  當我們的表中存在類似於 TEXT 或者是很大的 VARCHAR型別的大欄位的時候,如果我們大部分訪問這張表的時候都不需要這個欄位,我們就該義無反顧的將其拆分到另外的獨立表中,以減少常用資料所佔用的儲存空間。這樣做的一個明顯好處就是每個資料塊中可以儲存的資料條數可以大大增加,既減少物理 IO 次數,也能大大提高記憶體中的快取命中率。

  上面幾點的優化都是為了減少每條記錄的儲存空間大小,讓每個資料庫中能夠儲存更多的記錄條數,以達到減少 IO 操作次數,提高快取命中率。下面這個優化建議可能很多開發人員都會覺得不太理解,因為這是典型的反正規化設計,而且也和上面的幾點優化建議的目標相違背。

  適度冗餘

  為什麼我們要冗餘?這不是增加了每條資料的大小,減少了每個資料塊可存放記錄條數嗎?

  確實,這樣做是會增大每條記錄的大小,降低每條記錄中可存放資料的條數,但是在有些場景下我們仍然還是不得不這樣做:

  1.被頻繁引用且只能通過 Join 2張(或者更多)大表的方式才能得到的獨立小欄位。

  2.這樣的場景由於每次Join僅僅只是為了取得某個小欄位的值,Join到的記錄又大,會造成大量不必要的 IO,完全可以通過空間換取時間的方式來優化。不過,冗餘的同時需要確保資料的一致性不會遭到破壞,確保更新的同時冗餘欄位也被更新。

  儘量使用 NOT NULL

  NULL 型別比較特殊,SQL 難優化。雖然 MySQL NULL型別和 Oracle 的NULL 有差異,會進入索引中,但如果是一個組合索引,那麼這個NULL 型別的欄位會極大影響整個索引的效率。此外,NULL 在索引中的處理也是特殊的,也會佔用額外的存放空間。

  很多人覺得 NULL 會節省一些空間,所以儘量讓NULL來達到節省IO的目的,但是大部分時候這會適得其反,雖然空間上可能確實有一定節省,倒是帶來了很多其他的優化問題,不但沒有將IO量省下來,反而加大了SQL的IO量。所以儘量確保 DEFAULT 值不是 NULL,也是一個很好的表結構設計優化習慣。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24104518/viewspace-722697/,如需轉載,請註明出處,否則將追究法律責任。

相關文章