做后台開發的程序猿通常需要寫各種各樣的sql,可很多時候寫出來的sql雖然能滿足功能性需求,性能上卻不盡人意。如果業務復雜,表結構和索引設計又不合理的話,寫出來的sql執行時間可能會達到幾十甚至上百秒,對於生產環境來說,這是相當恐怖的一件事。因此,了解一些常見的mysql優化技巧很有必要。本文將從表結構和索引設計,sql執行原理,sql編寫優化3方面進行分析和講解,希望能對大家有所幫助。
1、表結構,字段設計是否合理?
這是最基礎也是最容易忽視的一個環節。良好的表結構設計是sql優化的基礎,在這個存儲廉價,空間足夠的時代,設計表的過程中,不一定要完全滿足范式理論,我們可以通過適當的冗余設計,避免連表查詢,達到以空間來換取時間的目的。設計表的時候,我們會根據業務需求來決定建幾個表,表之間通過哪些外鍵來關聯。而且通常需要考慮到數據規模(單表記錄數最好不要超過千萬,如果超過可能需要分表分區,包括垂直分表和水平分表)、查詢更新頻率(哪些字段經常用於查詢,哪些經常用於更新),各字段的類型和長度取值,在哪些字段上建哪種類型的索引等等。
比方說,如果你是innodb存儲引擎,那么你的主鍵最好設計成自增的,這樣效率最高。因為innodb存儲引擎的索引是基於B+樹實現,如果采用自增設計,就能快速找到插入節點的位置進行插入或刪除,對其他節點影響較小,避免頻繁分裂樹結構。有的公司設計表的時候喜歡采用UUID的方式來作為主鍵,這樣的好處是數據遷移的時候,主鍵不會變,能找到對應關系,但是會有2個問題:1、UUID的長度是36位,占用字節較長,尤其對於innoDB來說,建立輔助索引的時候,輔助索引里存儲的都是主鍵的值,這會導致輔助索引占據空間變大。2、UUID是無序的,每次插入或者刪除一條記錄的時候,為了維持索引的特性,可能會導致節點頻繁分裂,這樣非常影響效率。
在設計字段的時候,盡量采用整形的,比如用tinyint 代替char(1),這樣便於存儲和計算。在滿足業務的前提下,長度越短越好,如果有大對象,比如text或blob類型的字段,並且這些字段查詢頻率較低時,可以考慮拆表來單獨存儲(也就是垂直分表),避免對主表造成影響。此外,設計表的時候,最好設計為not null,因為允許為null時,mysql還需要有個字節來標識是否是null,而且mysql索引無法存儲null,如果在一列允許null 的索引中使用where colum is null,那么mysql是不會走索引的。那如果有的字段就是沒值怎么辦?可以用空字符串或者0這些代替。
2、sql執行原理
寫好了sql后,sql是怎么執行的呢?當我們運行sql的時候,會經歷客戶端發送請求,服務端接受請求並解析sql,生成sql執行計划,執行並將結果返回給客戶端這些過程。要優化sql,首先要知道sql到底在哪些環節花了多長時間。這里不去分析網絡因素對sql造成的影響,我們只需關注sql生成的執行計划,這個執行計划能很大程度上幫助我們找到優化sql的方向。那怎么看sql的執行計划呢?explain 你的sql。比如在mysql 5.6自帶的sakila數據庫上執行如下sql:
可以看到有id,select_type,partitions,type,possible_keys等等內容。首先說一下,比較重要的有id,select_type,type(相當重要),key(相當重要),key_len(可能重要),extra(相當重要)這幾列。其他的列就不介紹了。這些內容都代表什么意義呢?
id通常表示執行順序,比如有3行,id分別為1,1,2,那么執行順序就是1,1,2,通常id的個數對應select的個數。
select_type表示查詢類型,主要有以下幾種:
SIMPLE:簡單SELECT(不使用UNION或子查詢等)
PRIMARY:最外面的SELECT
UNION:UNION中的第二個或后面的SELECT語句
DEPENDENT UNION:UNION中的第二個或后面的SELECT語句,取決於外面的查詢
UNION RESULT:UNION的結果。
SUBQUERY:子查詢中的第一個SELECT
DEPENDENT SUBQUERY:子查詢中的第一個SELECT,取決於外面的查詢
DERIVED:導出表的SELECT(FROM子句的子查詢)
type:表示使用了哪種類別的連接,有無使用索引,是使用Explain命令分析性能瓶頸的關鍵項之一,性能由好到壞依次為:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL。一般來說,得保證查詢至少達到range級別,最好能達到ref,否則就可能會出現性能問題。
key:表示使用的索引,如果沒有選擇索引,則為NULL。
key_len:表示索引長度,對於單列索引,該值意義不大,對於聯合索引,則有重要作用,key_len的大小顯示了聯合索引中真正用到的哪幾列,如果是聯合索引,則該值越大表示走的索引列越多,查詢效率越高,這里涉及到索引前綴的知識,該部分后面有空再講。對於該列的值,也有計算公式:如果是單列索引,則key_len=索引列的長度*字符編碼占用的字節數(UTF8編碼為3字節,GBK為2字節,latin為1字節)+標識是否允許null的字節數(1字節)+內容長度(針對可變長列,1字節),舉個例子:
該表中,city_id是主鍵,city字段是varchar類型,長度為50,默認為null,執行explain select city from sakila.city,如下:
可以發現,這里走了覆蓋索引,順便提下,覆蓋索引就是sql的查詢內容通過走sql索引就能查到,這種情況就是覆蓋索引,所以這里我們看到,即使我們不加where條件也能走索引。索引列是city_name,key_len為152,怎么來的呢?對照上面的公式:50長度*3(UTF8編碼一個字符3個字節)+1(標識是否為null)+1(標識內容的長度),這樣是不是很清晰了?
最后這列Extra:包含MySQL解決查詢的詳細信息,也是關鍵參考項之一。當這列出現了Using filesort(出現這種情況九死一生,很有必要優化)和Using temporary(這里就是十死0生了,必須優化!)就需要格外注意了。
3、優化你的sql
當完成了上面2步以后,如果發現你的sql很慢,這時候就必須對我們的sql進行優化了。2個大的思路是先問問自己:是否建了索引?索引建的是否合適?當我們分析一條sql慢的時候,我們需要考慮,這條sql查詢的內容是否建了索引呢?如果沒有,那要在哪列建哪種索引呢?比如我們要從用戶表(>100W條記錄)中根據姓名查某個用戶,如果沒有建索引,顯然會很慢,那么怎么建索引呢?你可能會說很簡單嘛,就在姓名上建個索引不就完了嘛。那假如(只是假如)姓名這列里,100W個用戶中,有50W個叫張三的,20W個叫李四的,30W個王五的,你在這里建合適嗎?顯然不合適,或者說,僅僅對這列建單列索引不合適,因為選擇性太差。而且這會導致個問題,當sql存儲引擎發現走全表掃描比走索引更快的時候,它會放棄走索引,直接掃表。這里有個最重要的關鍵詞:選擇性,選擇性可以理解為:該表中該列的不重復數/總記錄數,該比值在0-1之間,越接近1說明選擇性越好,唯一索引的選擇性就是1,因此唯一索引是性能最好的索引。像上面用戶表中,該表的選擇性我們可以這么查:select count(distinct name)/count(*) from customer;因此我們要做的,就是想辦法提高索引的選擇性,可以采用建聯合索引,或者部分索引(就是取該列的N個字符來建索引,但是這種索引不能用於group by中)等等,遵循這個思路,我們就明白,有的開發員在性別列建索引,其實並不是一個好選擇,因為選擇性太差。要建高效的索引,就一定是選擇性好的索引。
端午假期第一天,上午看了會世界杯,下午閑的無聊寫了這篇博客,歡迎拍磚交流,轉載請務必注明出處,謝謝。