優化大綱:
- 通過explain 語句幫助選擇更好的索引和寫出更優化的查詢語句。
- SQL語句中的IN包含的值不應該過多。
- 當只需要一條數據的時候,使用limit 1。
- 如果限制條件中其他字段沒有索引,盡量少用or。
- 盡量用union all代替union。
- 不使用ORDER BY RAND()。
- 區分in和exists、not in和not exists。
- 使用合理的分頁方式以提高分頁的效率。
- 查詢的數據過大,可以考慮使用分段來進行查詢。
- 避免在where子句中對字段進行null值判斷。
- 避免在where子句中對字段進行表達式操作。
- 必要時可以使用force index來強制查詢走某個索引。
- 注意查詢范圍,between、>、<等條件會造成后面的索引字段失效。
- 關於JOIN優化。
優化使用
1、mysql explane 用法
explane顯示了mysql如何使用索引來處理select語句以及連接表。可以幫助更好的索引和寫出更優化的查詢語句。
EXPLAIN SELECT * FROM l_line WHERE `status` = 1 and create_at > '2019-04-11';
explain字段列說明
table:顯示這一行的數據是關於哪張表的
type:這是重要的列,顯示連接使用了何種類型。從最好到最差的連接類型為const、eq_reg、ref、range、indexhe和all
possible_keys:顯示可能應用在這張表中的索引。如果為空,沒有可能的索引。可以為相關的域從where語句中選擇一個合適的語句
key: 實際使用的索引。如果為null,則沒有使用索引。很少的情況下,mysql會選擇優化不足的索引。這種情況下,可以在select語句中使用use index(indexname)來強制使用一個索引或者用ignore index(indexname)來強制mysql忽略索引
key_len:使用的索引的長度。在不損失精確性的情況下,長度越短越好
ref:顯示索引的哪一列被使用了,如果可能的話,是一個常數
rows:mysql認為必須檢查的用來返回請求數據的行數
extra:關於mysql如何解析查詢的額外信息。但這里可以看到的壞的例子是using temporary和using filesort,意思mysql根本不能使用索引,結果是檢索會很慢
extra列返回的說明
distinct: 一旦mysql找到了與行相聯合匹配的行,就不在搜索了。
not exists: mysql優化了left join,一旦找到了匹配left join標准的行,就不在搜索了。
range checked for each record(index map:#): 沒有找到理想的索引,因此對於從前面表中來的每一個行組合,mysql檢查使用哪個索引,並用它來從表中返回行。這是使用索引的最慢的連接之一。
using filesort: 看到這個的時候,查詢就需要優化了。mysql需要行額外的步驟來發現如何對返回的行排序。它根據連接類型以及存儲排序鍵值和匹配條件的全部行的行指針來排序全部行。
using index: 列數據是從僅僅使用了索引中的信息而沒有讀取實際的行動的表返回的,這發生在對表的全部的請求列都是同一個索引的部分的時候。
using temporary : 看到這個的時候,查詢需要優化了。這里,mysql需要創建一個臨時表來存儲結果,這通常發生在對不同的列集進行order by上,而不是group by上。
where used: 使用了where從句來限制哪些行將與下一張表匹配或者是返回給用戶。如果不想返回表中的全部行,並且連接類型all或index,這就會發生,或者是查詢有問題不同連接類型的解釋(按照效率高低的順序排序)。
system 表只有一行:system表。這是const連接類型的特殊情況。
const:表中的一個記錄的最大值能夠匹配這個查詢(索引可以是主鍵或惟一索引)。因為只有一行,這個值實際就是常數,因為mysql先讀這個值然后把它當做常數來對待。
eq_ref: 在連接中,mysql在查詢時,從前面的表中,對每一個記錄的聯合都從表中讀取一個記錄,它在查詢使用了索引為主鍵或惟一鍵的全部時使用。
ref: 這個連接類型只有在查詢使用了不是惟一或主鍵的鍵或者是這些類型的部分(比如,利用最左邊前綴)時發生。對於之前的表的每一個行聯合,全部記錄都將從表中讀出。這個類型嚴重依賴於根據索引匹配的記錄多少—越少越好。
range: 這個連接類型使用索引返回一個范圍中的行,比如使用>或<查找東西時發生的情況。
index: 這個連接類型對前面的表中的每一個記錄聯合進行完全掃描(比all更好,因為索引一般小於表數據)。
all: 這個連接類型對於前面的每一個記錄聯合進行完全掃描,這一般比較糟糕,應該盡量避免。
2、SQL語句中IN包含的值不應過多
MySQL對於IN做了相應的優化,即將IN中的常量全部存儲在一個數組里面,而且這個數組是排好序的。但是如果數值較多,產生的消耗也是比較大的。再例如:select id from t where num in(1,2,3) 對於連續的數值,能用between就不要用in了;再或者使用連接來替換。
3、當只需要一條數據的時候,使用limit 1
這是為了使EXPLAIN中type列達到const類型。
4、如果限制條件中其他字段沒有索引,盡量少用or
or兩邊的字段中,如果有一個不是索引字段,而其他條件也不是索引字段,會造成該查詢不走索引的情況。很多時候使用union all或者是union(必要的時候)的方式來代替“or”會得到更好的效果。
5、盡量用union all代替union
union和union all的差異主要是前者需要將結果集合並后再進行唯一性過濾操作,這就會涉及到排序,增加大量的CPU運算,加大資源消耗及延遲。當然,union all的前提條件是兩個結果集沒有重復數據。
6、 不適用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.nidlimit 1000;
7、 區分in和exists、not in 和not exists
總結: 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 not in (select b.id from B表);
高效的SQL語句:
select colname … from A表 Left join B表 on where a.id = b.id and b.id is null
8、 使用合理的分頁方式以提高分頁的效率
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;
9、 查詢的數據過大,可以考慮使用分段來進行查詢
在一些用戶選擇頁面中,可能一些用戶選擇的時間范圍過大,造成查詢緩慢。主要的原因是掃描行數過多。這個時候可以通過程序,分段進行查詢,循環遍歷,將結果合並處理進行展示。
10、 避免在where子句中對字段進行null值判斷
對於null的判斷會導致引擎放棄使用索引而進行全表掃描。
11、避免在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;
12、必要時可以使用force index來強制查詢走某個索引
有的時候MySQL優化器采取它認為合適的索引來檢索SQL語句,但是可能它所采用的索引並不是我們想要的。這時就可以采用forceindex來強制優化器使用我們制定的索引。
13、注意查詢范圍,between、>、<等條件會造成后面的索引字段失效。
對於聯合索引來說,如果存在范圍查詢,比如between、>、<等條件時,會造成后面的索引字段失效。
14、 關於JOIN優化
LEFT JOIN A表為驅動表,INNER JOIN MySQL會自動找出那個數據少的表作用驅動表,RIGHT JOIN B表為驅動表。
注意:
1)MySQL中沒有full join,可以用以下方式來解決:
select * from A left join B on B.name = A.namewhere B.name is null union all select * from B;
2)盡量使用inner join,避免left join:
參與聯合查詢的表至少為2張表,一般都存在大小之分。如果連接方式是inner join,在沒有其他過濾條件的情況下MySQL會自動選擇小表作為驅動表,但是left join在驅動表的選擇上遵循的是左邊驅動右邊的原則,即left join左邊的表名為驅動表。
3)合理利用索引:
被驅動表的索引字段作為on的限制字段。
4)利用小表去驅動大表:
從小表去驅動大表,可以有效減少嵌套循環中的循環次數,已減少I/O總量以及CPU運算的次數。
5)巧用STRAIGHT_JOIN:
inner join是由MySQL選擇驅動表,但是有些特殊情況需要選擇另個表作為驅動表,比如有group by、order by等「Using filesort」、「Using temporary」時。STRAIGHT_JOIN來強制連接順序,在STRAIGHT_JOIN左邊的表名就是驅動表,右邊則是被驅動表。在使用STRAIGHT_JOIN有個前提條件是該查詢是內連接,也就是inner join。其他鏈接不推薦使用STRAIGHT_JOIN,否則可能造成查詢結果不准確。
是有些特殊情況需要選擇另個表作為驅動表,比如有group by、order by等「Using filesort」、「Using temporary」時。STRAIGHT_JOIN來強制連接順序,在STRAIGHT_JOIN左邊的表名就是驅動表,右邊則是被驅動表。在使用STRAIGHT_JOIN有個前提條件是該查詢是內連接,也就是inner join。其他鏈接不推薦使用STRAIGHT_JOIN,否則可能造成查詢結果不准確。
其它的優化總結
1.對查詢進行優化,應盡量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。
2.應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:
select id from t where num is null
可以在num上設置默認值0,確保表中num列沒有null值,然后這樣查詢:
select id from t where num=0
3.應盡量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。
4.應盡量避免在 where 子句中使用 or 來連接條件,否則將導致引擎放棄使用索引而進行全表掃描,如:
select id from t where num=10 or num=20
可以這樣查詢:
select id from t where num=10 union all select id from t where num=20
5.in 和 not in 也要慎用,否則會導致全表掃描,如:
select id from t where num in(1,2,3)
對於連續的數值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
6.下面的查詢也將導致全表掃描:
select id from t where name like '%abc%'
7.應盡量避免在 where 子句中對字段進行表達式操作,這將導致引擎放棄使用索引而進行全表掃描。如:
select id from t where num/2=100
應改為:
select id from t where num=100*2
8.應盡量避免在where子句中對字段進行函數操作,這將導致引擎放棄使用索引而進行全表掃描。如:
select id from t where substring(name,1,3)='abc'--name以abc開頭的id
應改為:
select id from t where name like 'abc%'
9.不要在 where 子句中的“=”左邊進行函數、算術運算或其他表達式運算,否則系統將可能無法正確使用索引。
10.在使用索引字段作為條件時,如果該索引是復合索引,那么必須使用到該索引中的第一個字段作為條件時才能保證系統使用該索引,
否則該索引將不會被使用,並且應盡可能的讓字段順序與索引順序相一致。
11.不要寫一些沒有意義的查詢,如需要生成一個空表結構:
select col1,col2 into #t from t where 1=0
這類代碼不會返回任何結果集,但是會消耗系統資源的,應改成這樣:
create table #t(...)
12.很多時候用 exists 代替 in 是一個好的選擇:
select num from a where num in(select num from b)
用下面的語句替換:
select num from a where exists(select 1 from b where num=a.num)
注意:IN適合於外表大而內表小的情況;EXISTS適合於外表小而內表大的情況
13.並不是所有索引對查詢都有效,SQL是根據表中數據來進行查詢優化的,當索引列有大量數據重復時,SQL查詢可能不會去利用索引, 如一表中有字段sex,male、female幾乎各一半,那么即使在sex上建了索引也對查詢效率起不了作用。
14.索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,
因為 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。
一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有必要。
15.盡量使用數字型字段,若只含數值信息的字段盡量不要設計為字符型,這會降低查詢和連接的性能,並會增加存儲開銷。
這是因為引擎在處理查詢和連接時會逐個比較字符串中每一個字符,而對於數字型而言只需要比較一次就夠了。
16.盡可能的使用 varchar 代替 char ,因為首先變長字段存儲空間小,可以節省存儲空間,
其次對於查詢來說,在一個相對較小的字段內搜索效率顯然要高些。
17.任何地方都不要使用 select * from t ,用具體的字段列表代替“*”,不要返回用不到的任何字段。
18.避免頻繁創建和刪除臨時表,以減少系統表資源的消耗。
19.臨時表並不是不可使用,適當地使用它們可以使某些例程更有效,例如,當需要重復引用大型表或常用表中的某個數據集時。但是,對於一次性事件,最好使用導出表。
20.在新建臨時表時,如果一次性插入數據量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數據量不大,為了緩和系統表的資源,應先create table,然后insert。
21.如果使用到了臨時表,在存儲過程的最后務必將所有的臨時表顯式刪除,先 truncate table ,然后 drop table ,這樣可以避免系統表的較長時間鎖定。
22.盡量避免使用游標,因為游標的效率較差,如果游標操作的數據超過1萬行,那么就應該考慮改寫。
23.使用基於游標的方法或臨時表方法之前,應先尋找基於集的解決方案來解決問題,基於集的方法通常更有效。
24.與臨時表一樣,游標並不是不可使用。對小型數據集使用 FAST_FORWARD 游標通常要優於其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的數據時。
在結果集中包括“合計”的例程通常要比使用游標執行的速度快。如果開發時間允許,基於游標的方法和基於集的方法都可以嘗試一下,看哪一種方法的效果更好。
25.盡量避免大事務操作,提高系統並發能力。
26.盡量避免向客戶端返回大數據量,若數據量過大,應該考慮相應需求是否合理。
以上無法解決可以考慮:分庫分表和讀寫分離