專案中常用的 19 條 MySQL 優化方案
宣告一下:下面的優化方案都是基於 “ Mysql-索引-BTree型別 ” 的
一、EXPLAIN
做MySQL優化,我們要善用 EXPLAIN 檢視SQL執行計劃。
下面來個簡單的示例,標註(1,2,3,4,5)我們要重點關注的資料:
type列
,連線型別。一個好的sql語句至少要達到range級別。杜絕出現all級別key列
,使用到的索引名。如果沒有選擇索引,值是NULL。可以採取強制索引方式key_len列
,索引長度rows列
,掃描行數。該值是個預估值extra列
,詳細說明。注意常見的不太友好的值有:Using filesort, Using temporary
二、SQL語句中IN包含的值不應過多
MySQL對於IN做了相應的優化,即將IN中的常量全部儲存在一個數組裡面,而且這個陣列是排好序的。但是如果數值較多,產生的消耗也是比較大的。再例如:select id from t where num in(1,2,3)
對於連續的數值,能用 between
就不要用in
了;再或者使用連線來替換。
三、SELECT語句務必指明欄位名稱
SELECT *增加很多不必要的消耗(cpu、io、記憶體、網路頻寬);增加了使用覆蓋索引的可能性;當表結構發生改變時,前斷也需要更新。所以要求直接在select後面接上欄位名。
四、當只需要一條資料的時候,使用limit 1
這是為了使EXPLAIN中type列達到const
五、如果排序欄位沒有用到索引,就儘量少排序
六、如果限制條件中其他欄位沒有索引,儘量少用or
or
兩邊的欄位中,如果有一個不是索引欄位,而其他條件也不是索引欄位,會造成該查詢不走索引的情況。很多時候使用union all
或者是union
(必要的時候)的方式來代替“or”
會得到更好的效果
七、儘量用union all代替union
union
和union all
的差異主要是前者需要將結果集合並後再進行唯一性過濾操作,這就會涉及到排序,增加大量的CPU運算,加大資源消耗及延遲。當然,union all
的前提條件是兩個結果集沒有重複資料。
八、不使用ORDER BY RAND()
select id from `dynamic` order by rand() limit 1000;
上面的sql語句,可優化為
select id from`dynamic` t1 join (select rand()*(select max(id)from`dynamic`)as nid) t2 on t1.id > t2.nid limit 1000;
九、區分in和exists, not in和not exists
select*from表A where id in(select id from表B)
上面sql語句相當於
select*from表A where exists(select*from表B where表B.id=表A.id)
區分in
和exists
主要是造成了驅動順序的改變(這是效能變化的關鍵),如果是exists
,那麼以外層表為驅動表,先被訪問,如果是IN,那麼先執行子查詢。所以IN適合於外表大而內表小的情況;EXISTS
適合於外表小而內表大的情況。關於not in
和not exists
,推薦使用not exists
,不僅僅是效率問題,not in
可能存在邏輯問題。如何高效的寫出一個替代not exists
的sql語句?
原sql語句
select colname …from A表where a.id notin(select b.id from B表)
高效的sql語句
select colname …from A表Left join B表 on where a.id = b.id where b.id isnull
取出的結果集如下圖表示,A表不在B表中的資料
十、使用合理的分頁方式以提高分頁的效率
select id,name from product limit 866613,20
使用上述sql語句做分頁的時候,可能有人會發現,隨著表資料量的增加,直接使用limit分頁查詢會越來越慢。
優化的方法如下:可以取前一頁的最大行數的id,然後根據這個最大的id來限制下一頁的起點。比如此列中,上一頁最大的id是866612。sql可以採用如下的寫法:
select id,name from product where id>866612 limit 20
十一、分段查詢
在一些使用者選擇頁面中,可能一些使用者選擇的時間範圍過大,造成查詢緩慢。主要的原因是掃描行數過多。這個時候可以通過程式,分段進行查詢,迴圈遍歷,將結果合併處理進行展示。
如下圖這個sql語句,掃描的行數成百萬級以上的時候就可以使用分段查詢
十二、避免在 where 子句中對欄位進行 null 值判斷
對於null
的判斷會導致引擎放棄使用索引而進行全表掃描
。
十三、不建議使用%字首模糊查詢
例如LIKE “%name”
或者LIKE “%name%”
,這種查詢會導致索引失效而進行全表掃描。但是可以使用LIKE “name%”
。
那如何查詢%name%
?
如下圖所示,雖然給secret
欄位添加了索引,但在explain
結果果並沒有使用
那麼如何解決這個問題呢,答案:使用全文索引
在我們查詢中經常會用到select id,fnum,fdst from dynamic201606 where username like '%zhangsan%';
。這樣的語句,普通索引是無法滿足查詢需求的。慶幸的是在MySQL中,有全文索引來幫助我們。
建立全文索引的sql語法是:
ALTER TABLE `dynamic_201606` ADD FULLTEXT INDEX `idx_user_name`(`user_name`);
使用全文索引的sql語句是:
select id,fnum,fdst from dynamic_201606 where match(user_name) against('zhangsan'inboolean mode);
注意:在需要建立全文索引之前,請聯絡DBA確定能否建立。同時需要注意的是查詢語句的寫法與普通索引的區別
十四、避免在where子句中對欄位進行表示式操作
比如
select user_id,user_project from user_base where age*2=36;
中對欄位就行了算術運算,這會造成引擎放棄使用索引,建議改成
select user_id,user_project from user_base where age=36/2;
十五、避免隱式型別轉換
where
子句中出現 column
欄位的型別和傳入的引數型別不一致的時候發生的型別轉換,建議先確定where中的引數型別
十六、對於聯合索引來說,要遵守最左字首法則
舉列來說索引含有欄位id,name,school
,可以直接用id
欄位,也可以id,name
這樣的順序,但是name;school
都無法使用這個索引。所以在建立聯合索引的時候一定要注意索引欄位順序,常用的查詢欄位放在最前面
十七、必要時可以使用force index來強制查詢走某個索引
有的時候MySQL優化器採取它認為合適的索引來檢索sql語句,但是可能它所採用的索引並不是我們想要的。這時就可以採用force index來強制優化器使用我們制定的索引。
十八、注意範圍查詢語句
對於聯合索引來說,如果存在範圍查詢,比如between
,>
,<
等條件時,會造成後面的索引欄位失效。
十九、關於JOIN優化
1
LEFT JOIN
A表為驅動表INNER JOIN
MySQL會自動找出那個資料少的表作用驅動表RIGHT JOIN
B表為驅動表
full join
,可以用以下方式來解決select*from A left join B on B.name = A.name
where B.name isnullunion all
select*from B;
- 儘量使用
inner join
,避免left join
參與聯合查詢的表至少為2張表,一般都存在大小之分。如果連線方式是inner join
,在沒有其他過濾條件的情況下MySQL會自動選擇小表作為驅動表,但是left join
在驅動表的選擇上遵循的是左邊驅動右邊的原則,即left join
左邊的表名為驅動表。
- 合理利用索引
被驅動表的索引欄位作為on的限制欄位。
- 利用小表去驅動大表
從原理圖能夠直觀的看出如果能夠減少驅動表的話,減少巢狀迴圈中的迴圈次數,以減少 IO總量及CPU運算的次數。
- 巧用
STRAIGHT_JOIN
inner join
是由mysql選擇驅動表,但是有些特殊情況需要選擇另個表作為驅動表,比如有group by
、order by
等「Using filesort」、「Using temporary」時。STRAIGHTJOIN來強制連線順序,在STRAIGHTJOIN
左邊的表名就是驅動表,右邊則是被驅動表。在使用STRAIGHTJOIN
有個前提條件是該查詢是內連線,也就是inner join
。其他連結不推薦使用STRAIGHTJOIN
,否則可能造成查詢結果不準確。
這個方式有時可能減少3倍的時間。