高性能MySQL-Schema與數據類型優化

良好的邏輯設計和物理設計是高性能的基石,應該根據系統將要執行的查詢語句設計schema,但記住這往往需要權衡各種因素。例如,反范式的設計可以加快某些類型的查詢,但同時也可能使一些查詢變慢。比如添加計數表和匯總表是很好的優化查詢的方式,但這些表的維護成本很高。

==================================================================

1.1 選擇優化的數據類型

MySQL支持的數據類型非常多,選擇正確的數據類型對于獲得高性能至關重要。以下羅列選擇的原則:

1)更小的通常更好。一般情況下,應該使用可以正確存儲數據的最小數據類型。例如,只需存儲0-200整數,那么相比 int 來說 tinyint unsigned 更好。更小的數據類型通常更快,占用更少的磁盤空間、內存、CPU緩存,并且處理時需要的CPU周期也更少。但是需要確保沒有低估存儲值的范圍,因為未來要在schema中多個地方增加數據類型的范圍是一個耗時和痛苦的操作。

2)簡單就好。簡單數據類型的操作通常需要更少的CPU周期,例如整數型比字符型操作代價更低,因為字符集和校對規則(排序規則)使字符比較比整型更復雜。另外的例子,使用MySQL內建的date、time、datetime來存儲日期和時間,而不是使用字符串,還有使用整型存儲IP地址,也不要使用字符型。

3)盡量避免使用NULL。創建時盡量使用NOT NULL,因為NULL值會使該列的索引、索引統計、值的比較都更復雜,而且會使用更多存儲空間。特別是一定要在列上建立索引時,一定是NOT NULL。

如何選擇數據類型,第一步,需要確定合適的大類型,第二步,在大類型中選擇小類型。

==================================================================

1.1.1 整數類型

整數類型主要包括 TINYINT(8位,-128~127)、SMALLINT(16位)、MEDIUMINT(24位)、INT(32位)、BIGINT(64位),他們的存儲的值的范圍為:-2^(N-1)至2^(N-1)-1,以上都可以使用UNSIGNED,取值范圍為0~2^N-1。

注意:這個選擇僅影響數據在內存和硬盤中的存儲,不會對整數的計算產生影響,因為MySQL計算時均轉換為64位 BIGINT 進行。

注意:MySQL可以為整數類型指定寬度,這只是顯示的寬度,如INT(5),顯示5個字符寬度,如123,會顯示00123,因此不會影響取值,也就是說比如INT(5)和INT(11)在存儲和計算時是相同的,沒有區別。

==================================================================

1.1.2 實數類型

實數類型包括 FLOAT(不精確類型)、DOUBLE(不精確類型)、DECIMAL(精確類型),支持指定精度,如DECIMAL(18,9),表示小數點兩邊各9個數字。

注意:由于指定精度,會使得MySQL悄悄地選擇不同的數據類型,或者在存儲時對值自動取舍,因此建議只指定數據類型,不指定精度,精度根據需要在前端取舍。

注意:如果真需要DECIMAL,建議使用乘以10^N取得整數后使用INT(或BIGINT)數據類型,前端顯示時根據需要除以10^N獲得正確的結果,或者將指數N對應以INT存入數據庫,這樣使MySQL得到優化。

==================================================================

4.1.3 字符串類型

MySQL有多種字符串類型,每種可以定義不同的字符集、排序規則(校對規則),這些東西會很大程度上影響性能。

VARCHAR和CHAR是兩種基本的類型,不幸的是,很難解釋清楚兩種類型是怎么存儲在磁盤和內存中的,因為這跟存儲引擎有關。不同的存儲引擎(MyISAM、InnoDB、。。。)都不同。

VARCHAR類型,用于存儲可變長字符串,它比定長類型節省空間,因為它僅使用必要的空間,即,越短的字符串使用越少空間。除非使用 ROW_FORMAT=FIXED,每行定長,這樣會浪費空間。另外,VARCHAR使用1~2個額外字節記錄字符串的長度,如果列長度小于或等于255,使用1個字節表示(8位),如果大于255,使用2個字節。

VARCHAR類型,雖然節約了空間,但由于行是變長的,在UPDATE時可能會使行變得更長,這就導致額外的工作;如果行變長后,頁內沒有更多空間可以存儲,這種情況下,就需要拆分片段(MyISAM)或分裂頁(InnoDB)來處理。

VARCHAR類型,適用情況,列最大長度比平均長度大很多,且很少更新,因為碎片將不是問題。

CHAR類型,是定長類型,根據指定的字符串長度分配足夠的空間;CHAR類型適合于存儲很短的字符串,或者所有值都接近同一個長度。例如,CHAR類型非常適合于存儲密碼的MD5值,因為MD5是一個定長的值。另一方面,CHAR類型也適合存儲經常變更的數據,因為定長的CHAR不容易產生碎片。對于非常短的列,CHAR比VARCHAR在存儲空間上也更有效率。如,用CHAR(1)存儲只有Y和N的值,如果用單字節字符集只需要一個字節,但VARCHAR(1)在單字節情況下則需要兩個字節,因為還有一個額外字節用于記錄長度。

注意:字符串長度定義不是字節數,而是字符數;多字節字符集會需要更多的空間存儲單個字符。

注意:字符集(Charset):是一個系統支持的所有抽象字符的集合。字符是各種文字和符號的總稱,包括各國家文字、標點符號、圖形符號、數字等。,常見字符集名稱:ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等;

注意:字符編碼(Character Encoding):是一套法則,使用該法則能夠對自然語言的字符的一個集合(如字母表或音節表),與其他東西的一個集合(如號碼或電脈沖)進行配對。即在符號集合與數字系統之間建立對應關系,它是信息處理的一項基本技術。通常人們用符號集合(一般情況下就是文字)來表達信息。而以計算機為基礎的信息處理系統則是利用元件(硬件)不同狀態的組合來存儲和處理信息的。元件不同狀態的組合能代表數字系統的數字,因此字符編碼就是將符號轉換為計算機可以接受的數字系統的數,稱為數字代碼。UTF8是編碼方法;

注意:慷慨是不明智的,比如,使用VARCHAR(5)或VARCHAR(100)存儲“hello”,空間開銷一樣,因為VARCHAR類型是可變長的,但因為MySQL通常會分配固定大小的內存塊來存儲內部值(意思是定義的長100>10,分配的內存塊就大?),尤其是使用內存臨時表進行排序或操作時會特別糟糕。在利用磁盤臨時表進行排序時也同樣糟糕。

BLOB和TEXT類型,BLOB以二進制存儲(1、0數據流,搞硬件編程的比較熟悉),TEXT以字符形式存儲,都是用來存儲很大數據的類型,注意,他們也是字符串類型,只是存儲形式不同。

BLOB和TEXT類型,在MySQL中會被作為對象處理,盡量避免使用。

使用枚舉(ENUM)代替字符串類型,枚舉會將列的長度壓縮到1個或2個字節,內部都保存為整數,而在表的.frm文件中保存“數字-字符串”的映射關系查找表。

注意:避免使用數字作為枚舉常量,比如 enum('1', '2', '3'),如果要枚舉的真是數字,不如直接使用 TINYINT 來得直接,正確的使用情況類似于 enum('private', 'group', 'public')。

注意:枚舉類型列排序時使用的是映射關系中的整數值,而不是按枚舉的字符串排序。所以最好的辦法是定義時就先排好序,如上面的例子,應寫為 column_type enum('group', 'private', 'public') not null default 'private'。

注意:需要保證枚舉列的字符串值能夠確定,否則未來要擴展類別,就需要使用 ALTER TABLE。

注意:因為枚舉保存為整數,因此查找時必須轉換為字符串,所以枚舉在執行查找時有一定開銷,尤其是當前表的枚舉列與其他表相同的VARCHAR列或CHAR列關聯查詢時,會較不使用枚舉更慢。例如 關聯條件為 ON A.ENUM_TYPE = B.CHAR_TYPE 時。但什么情況都需要一個平衡,使用枚舉可以縮減表的大小,可能會縮減30%;當然關聯后雖然慢,但可能會節省I/O。

==================================================================

1.1.4 日期和時間類型

MySQL提供許多類型保存日期和時間,最小粒度為秒。具體看看

DATETIME,存儲范圍1001年~9999年,封裝格式為 YYYYMMDDHHMMSS,與時區無關,8個字節(64位?),使用ANSI標準定義的日期和時間表示方法顯示,如“2014-01-11 22:05:33”,注意這是顯示。

TIMESTAMP,時間戳,記錄了一個累計秒數,從1970年1月1日午夜(格林尼治標準時間)以來。4個字節,與UNIXTIME相同。比DATETIME小很多了,最大記錄從 1970年~2038年的時間。轉換:FROM_UNIXTIME() 將時間戳轉換為日期,UNIX_TIMESTAMP() 將日期轉換為時間戳。

TIMESTAMP值與時區有關,DATETIME與時區無關,DATETIME記錄的是客戶端提交到數據庫的當地時間字符串。如果提交數據時不指定TIMESTAMP的值,則MySQL默認提供當前時間賦值,且默認NOT NULL。

注意:以上優勢,建議使用 TIMESTAMP,而不是 DATETIME,2038年以后怎么辦?

==================================================================

1.1.5 位數據類型

主要有 BIT 和 SET,避免使用。

==================================================================

1.1.6 選擇標識符(identifier)

標識列的數據類型,最好選擇整數,且符合上面的講的原則,能TINYINT的不INT;不要選用ENUM、SET、CHAR、VARCHAR,除非沒有辦法。

另外,標識列一般還與外鍵有關,所以記住,標識列不管在主表還是關聯表中,選擇的數據類型要完全一致,精確匹配,像 INT 和 UNSINGED INT 都會產生性能問題,即使沒有性能問題,也可能導致難以發現的錯誤。因為如果不是精確匹配,MySQL需要進行隱式類型轉換。

標識列不能使用字符型的原因,慢,消耗空間,對于MyISAM,字符型要使用壓縮索引,導致查詢慢6倍。且對于使用隨機生成的字符串更要注意,如使用MD5()、SHA1()、UUID(),這些新產生的值會分布在很大的空間范圍內,使得INSERT和SELECT都很慢,可能分布于不同的內存和磁盤不同位置,不會相鄰,來回掃描,導致整個數據集都成為熱數據。

==================================================================

1.1.7 特殊數據類型

兩個例子,低于秒級的存儲,IPv4的存儲,人們經常錯誤使用 VARCHAR(15)存儲IP地址,實際應使用32位 UNSIGNED INT存儲,因為IP地址本身就是整數,小數點只是為了顯示,因此應利用 MySQL提供的 INET_ATON()和INET_NTOA()切換。

==================================================================

1.2 MySQL Schema 設計中的陷阱

太多的列(上千個)、太多的關聯(單個查詢不要超過12個表)、全能的枚舉、變相的枚舉、非此發明的NULL(為避免不用NULL,不要亂給值如0000-00-00 00:00:00,給個0就好了)。

==================================================================

1.3 范式和反范式

一般都是先進行范式化,然后根據需要進行反范式化,取各自的有點進行折中。

范式的優點:表小、不重復、更新快;

范式的缺點:純粹范式,查詢基本都需要關聯,代價昂貴。

反范式的優點:反范式恰當的話,不需要關聯,快!

反范式的缺點:更新關聯數據的難處。

混用:實際中是混用,部分的范式化schema、緩存表、以及其他技巧,貌似是除了有經驗可以預先處理外,邊開發可以邊改動表的結構,將范式化的某些列,緩存到需要的主表中。

==================================================================

1.4 緩存表和匯總表(重要!!!)

緩存表:表示存儲那些可以比較簡單從schema其他表獲取數據的表,而這樣的數據每次獲取的速度又比較慢,且在邏輯上有冗余的數據。緩存表需要開發者決定是實時維護還是定期重建,定期重建不僅節省資源,還可以保持表不會有很多碎片。定期重建注意使用“影子表”保持原表的可用性。

匯總表:保存的是使用 GROUP BY 語句聚合數據的表,例如,數據不是邏輯上冗余的。

物化視圖:常被認為是一種功能,MySQL不提供這一功能;但實際上,物化視圖指的就是緩存表,以及與之相關的一系列解決方案。使用 Justin Swanhart 開發的 Flexviews 可以實現自己的物化視圖。實際使用中,只要你告訴 Flexviews 你希望從源數據庫或表提前信息的 Select 語句,Flexviews自己將其轉換,獲取結果存儲下來。開發者只需要面向 Flexviews查詢就行了,再不用查詢 數據庫。Flexviews能做到這樣,是因為 Flexviews 基于行的二進制日志更新數據,他的提前對象是數據庫日志。

計數器表:創建獨立計數器表記錄網站訪問量、用戶朋友數字、文件下載次數等,是個好主意,這種表小且快。但有一個問題,如果是InnoDB會執行行級鎖,如果是MyISAM會執行表級鎖,會造成高并發串行執行問題。書中列出了 使用 InnoDB 會用到的技巧(隨機槽、INSERT INTO...ON DUPLICATE KEY UPDATE)。

問題:以上內容,都在增加讀操作速度,但會造成寫操作變慢,甚至增加讀操作和寫操作的開發難度。

==================================================================

1.5 加快 ALTER TABLE 操作的速度

本節講到兩個重要技術,來通過速度,一是修改 .frm 文件,二是對于MyISAM表先載入數據,再創建索引。

==================================================================

1.6 總結

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容