前言
sql語句性能達不到你的要求,執行效率讓你忍無可忍,通常會時下面幾種狀況。mysql
- 網速不給力,不穩定。
- 服務器內存不夠,或者SQL 被分配的內存不夠。
- sql語句設計不合理
- 沒有相應的索引,索引不合理
- 沒有有效的索引視圖
- 表數據過大沒有有效的分區設計
- 數據庫設計太差,存在大量的數據冗餘
- 索引列上缺乏相應的統計信息,或者統計信息過時
- …
本片文章主要介紹的是如何sql優化方法跟技巧。sql
使用explain 分析你SQL的計劃
平常開發寫SQL的時候建議用explain分析一下本身書寫的SQL語句,尤爲是走不走索引這一塊。使用 Explain 關鍵字能夠模擬優化器執行SQL查詢語句,從而知道 MySQL 是如何處理你的 SQL 語句的。分析你的查詢語句或是表結構的性能瓶頸。數據庫
(1)語法:Explain + SQL語句
(2)執行計劃包含的信息(若是有分區表的話還會有partitions)
緩存
-
id
(select 查詢的序列號,包含一組數字,表示查詢中執行select子句或操做表的順序)。服務器- id相同,執行順序從上往下
- id全不一樣,若是是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行
- id部分相同,執行順序是先按照數字大的先執行,而後數字相同的按照從上往下的順序執行
-
select_type
(查詢類型,用於區別普通查詢、聯合查詢、子查詢等複雜查詢)網絡- SIMPLE :簡單的select查詢,查詢中不包含子查詢或UNION
- PRIMARY:查詢中若包含任何複雜的子部分,最外層查詢被標記爲PRIMARY
- SUBQUERY:在select或where列表中包含了子查詢
- DERIVED:在from列表中包含的子查詢被標記爲DERIVED,MySQL會遞歸執行這些子查詢,把結果放在臨時表裏
- UNION:若第二個select出如今UNION以後,則被標記爲UNION,若UNION包含在from子句的子查詢中,外層 select將被標記爲DERIVED
- UNION RESULT:從UNION表獲取結果的select
-
table
(顯示這一行的數據是關於哪張表的)數據庫設計 -
type
(顯示查詢使用了那種類型,從最好到最差依次排列system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
)函數- system:表只有一行記錄(等於系統表),是 const 類型的特例,平時不會出現
- const:表示經過索引一次就找到了,const 用於比較 primary key 或 unique 索引,由於只要匹配一行數據,因此很快,如將主鍵置於 where 列表中,mysql 就能將該查詢轉換爲一個常量
- eq_ref:惟一性索引掃描,對於每一個索引鍵,表中只有一條記錄與之匹配,常見於主鍵或惟一索引掃描
- ref:非惟一性索引掃描,範圍匹配某個單獨值得全部行。本質上也是一種索引訪問,他返回全部匹配某個單獨值的行,然而,它可能也會找到多個符合條件的行,多以他應該屬於查找和掃描的混合體
- range:只檢索給定範圍的行,使用一個索引來選擇行。key列顯示使用了哪一個索引,通常就是在你的where語句中出現了between、<、>、in等的查詢,這種範圍掃描索引比全表掃描要好,由於它只需開始於索引的某一點,而結束於另外一點,不用掃描所有索引
- index:Full Index Scan,index於ALL區別爲index類型只遍歷索引樹。一般比ALL快,由於索引文件一般比數據文件小。(也就是說雖然all和index都是讀全表,但index是從索引中讀取的,而all是從硬盤中讀的)
- ALL:Full Table Scan,將遍歷全表找到匹配的行
通常來講,得保證查詢至少達到range級別,最好到達ref
post
-
possible_keys
(顯示可能應用在這張表中的索引,一個或多個,查詢涉及到的字段若存在索引,則該索引將被列出,但不必定被查詢實際使用)性能 -
key
-
實際使用的索引,若是爲NULL,則沒有使用索引
-
查詢中若使用了覆蓋索引,則該索引和查詢的 select 字段重疊,僅出如今key列表中
-
-
key_len
- 表示索引中使用的字節數,可經過該列計算查詢中使用的索引的長度。在不損失精確性的狀況下,長度越短越好
- key_len顯示的值爲索引字段的最大可能長度,並不是實際使用長度,即key_len是根據表定義計算而得,不是經過表內檢索出的
-
ref
(顯示索引的哪一列被使用了,若是可能的話,是一個常數。哪些列或常量被用於查找索引列上的值) -
rows
(根據表統計信息及索引選用狀況,大體估算找到所需的記錄所須要讀取的行數) -
Extra
(包含不適合在其餘列中顯示但十分重要的額外信息)-
using filesort: 說明mysql會對數據使用一個外部的索引排序,不是按照表內的索引順序進行讀取。mysql中沒法利用索引完成的排序操做稱爲「文件排序」。常見於order by和group by語句中
-
Using temporary:使用了臨時表保存中間結果,mysql在對查詢結果排序時使用臨時表。常見於排序order by和分組查詢group by。
-
using index:表示相應的select操做中使用了覆蓋索引,避免訪問了表的數據行,效率不錯,若是同時出現using where,代表索引被用來執行索引鍵值的查找;不然索引被用來讀取數據而非執行查找操做
-
using where:使用了where過濾
-
using join buffer:使用了鏈接緩存
-
impossible where:where子句的值老是false,不能用來獲取任何元祖
-
select tables optimized away:在沒有group by子句的狀況下,基於索引優化操做或對於MyISAM存儲引擎優化COUNT(*)操做,沒必要等到執行階段再進行計算,查詢執行計劃生成的階段即完成優化
-
distinct:優化distinct操做,在找到第一匹配的元祖後即中止找一樣值的動做
-
舉例子:
-
第一行(執行順序4):id列爲1,表示是union裏的第一個select,select_type列的primary表示該查詢爲外層查詢,table列被標記爲,表示查詢結果來自一個衍生表,其中derived3中3表明該查詢衍生自第三個select查詢,即id爲3的select。【select d1.name…】
-
第二行(執行順序2):id爲3,是整個查詢中第三個select的一部分。因查詢包含在from中,因此爲derived。【select id,name from t1 where other_column=’’】
-
第三行(執行順序3):select列表中的子查詢select_type爲subquery,爲整個查詢中的第二個select。【select id from t3】
-
第四行(執行順序1):select_type爲union,說明第四個select是union裏的第二個select,最早執行【select name,id from t2】
-
第五行(執行順序5):表明從union的臨時表中讀取行的階段,table列的<union1,4>表示用第一個和第四個select的結果進行union操做。【兩個結果union操做】
關鍵字的慎用
避免判斷 null 值
應儘可能避免在 where 子句中對字段進行 null 值判斷,不然將致使引擎放棄使用索引從而進行全表掃描,如:select id from t where num is null
能夠在 num 上設置默認值 0,確保表中 num 列沒有 null 值,而後這樣查詢:
select id from t where num=0
避免使用 or 邏輯
應儘可能避免在 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
mysql是有優化器的,處於效率與成本考慮,遇到or條件,索引可能失效,看起來也合情合理。
慎用 in 和 not in 邏輯
in
和 not in
也要慎用,不然會致使全表掃描,如:
select id from t1 where num in(select id from t2 where id > 10)
此時外層查詢會全表掃描,不使用索引。能夠修改成:
select id from t1,(select id from t1 where id > 10)t2 where t1.id = t2.id
此時索引被使用,能夠明顯提高查詢效率。
Inner join 、left join、right join,優先使用Inner join,若是是left join,左邊表結果儘可能小
Inner join
內鏈接,在兩張表進行鏈接查詢時,只保留兩張表中徹底匹配的結果集left join
在兩張表進行鏈接查詢時,會返回左表全部的行,即便在右表中沒有匹配的記錄。right join
在兩張表進行鏈接查詢時,會返回右表全部的行,即便在左表中沒有匹配的記錄。
都知足SQL需求的前提下,推薦優先使用Inner join(內鏈接),若是要使用left join,左邊表數據結果儘可能小,若是有條件的儘可能放到左邊處理。
緣由:
- 若是inner join是等值鏈接,或許返回的行數比較少,因此性能相對會好一點。
- 同理,使用了左鏈接,左邊表數據結果儘可能小,條件儘可能放到左邊處理,意味着返回的行數可能比較少。
exists的合理使用
不少時候用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)
慎用distinct關鍵字
distinct
關鍵字通常用來過濾重複記錄,以返回不重複的記錄。在查詢一個字段或者不多字段的狀況下使用時,給查詢帶來優化效果。可是在字段不少的時候使用,卻會大大下降查詢效率。
-
反例:
SELECT DISTINCT * from user;
-
正例:
select DISTINCT name from user;
-
理由:
帶distinct
的語句cpu時間和佔用時間都高於不帶distinct的
語句。由於當查詢不少字段時,若是使用distinct,數據庫引擎就會對數據進行比較,過濾掉重複數據,然而這個比較,過濾的過程會佔用系統資源,cpu時間。
儘可能用 union all 替換 union
若是檢索結果中不會有重複的記錄,推薦union all 替換 union。
理由:若是使用union,無論檢索結果有沒有重複,都會嘗試進行合併,而後在輸出最終結果前進行排序。若是已知檢索結果沒有重複記錄,使用union all 代替union,這樣會提升效率。
查詢優化
GROUP BY關鍵字優化
- group by實質是先排序後進行分組,遵守索引建的最佳左前綴
- 當沒法使用索引列,增大
max_length_for_sort_data
參數的設置,增大sort_buffer_size
參數的設置 - where高於having,能寫在where限定的條件就不要去having限定了
查詢SQL儘可能不要使用select *,而是select具體字段
任何地方都不要使用 select * from t
,用具體的字段列表代替「*
」,不要返回用不到的任何字段。
優勢:
- 只取須要的字段,節省資源、減小網絡開銷。
select *
進行查詢時,極可能就不會使用到覆蓋索引了,就會形成回表查詢。
優化limit分頁
咱們平常作分頁需求時,通常會用 limit 實現,可是當偏移量特別大的時候,查詢效率就變得低下。
所以咱們有如下優化方案:
-
返回上次查詢的最大記錄(偏移量)
當偏移量最大的時候,查詢效率就會越低,由於Mysql並不是是跳過偏移量直接去取後面的數據,而是先把偏移量+要取的條數,而後再把前面偏移量這一段的數據拋棄掉再返回的,返回上次最大查詢記錄(偏移量),這樣能夠跳過偏移量,效率提高很多。 -
使用order by + 索引
使用order by+索引,也是能夠提升查詢效率的。 -
在業務容許的狀況下限制頁數
建議跟業務討論,有沒有必要查這麼後的分頁啦。由於絕大多數用戶都不會日後翻太多頁。
知道查詢結果爲一條記錄,建議使用limit 1
- 若是知道查詢結果只有一條或者只要最大/最小一條記錄,建議用
limit 1
,當加上limit 1
後,只要找到了對應的一條記錄,就不會繼續向下掃描了,效率將會大大提升。 - 固然,若是name是惟一索引的話,是沒必要要加上limit 1了,由於limit的存在主要就是爲了防止全表掃描,從而提升性能,若是一個語句自己能夠預知不用全表掃描,有沒有limit ,性能的差異並不大。
注意模糊查詢
若是用到模糊關鍵字查詢,很容易想到like,可是like極可能讓你的索引失效從而致使全表掃描,以下所示:
select id from t where name like '%abc%'
模糊查詢若是是必要條件時,可使用 select id from t where name like 'abc%'
來實現模糊查詢,此時索引將被使用。若是頭匹配是必要邏輯,建議使用全文搜索引擎(Elasticsearch、Lucene、Solr 等)。
- 把%放前面,並不走索引,把% 放關鍵字後面,仍是會走索引的。
避免查詢條件中字段計算
應儘可能避免在 where 子句中對字段進行表達式操做,這將致使引擎放棄使用索引而進行全表掃描。如:
select id from t where num/2=100
應改成:
select id from t where num=100*2
避免查詢條件中對字段進行函數操做
應儘可能避免在 where 子句中對字段進行函數操做,這將致使引擎放棄使用索引而進行全表掃描。
如:
select id from t where substring(name,1,3)='abc' --name 以 abc 開頭的 id
應改成:
select id from t where name like 'abc%'
緣由:須要什麼數據,就去查什麼數據,避免返回沒必要要的數據,節省開銷
避免不等值判斷
應儘可能避免在 where 子句中使用!=或<>操做符,不然將引擎放棄使用索引而進行全表掃描。
例如:
select age,name from user where age !=18;
以上使用!=或者<>極可能會致使索引失效從而進行全表掃描,因此咱們應該使用下面的方式:
//能夠考慮分開兩條sql寫 select age,name from user where age <18; select age,name from user where age >18;
對查詢進行優化,應考慮在 where 及 order by 涉及的列上創建索引,儘可能避免全表掃描。
where子句中考慮使用默認值代替null。
- 並非說使用了
is null
或者is not null
就會不走索引了,這個跟mysql版本以及查詢成本都有關。 - 若是把null值,換成默認值,不少時候讓走索引成爲可能,同時,表達意思會相對清晰一點。
where子句 「= 」 左邊注意點
不要在where
子句中的「=」
左邊進行函數、算術運算或其餘表達式運算,不然系統將可能沒法正確使用索引。
不要定義無心義的查詢
不要寫一些沒有意義的查詢,如須要生成一個空表結構:
select col1,col2 into #t from t where 1=0
這類代碼不會返回任何結果集,可是會消耗系統資源的,應改爲這樣:
create table #t(...)
索引優化
在適當的時候,使用覆蓋索引。
覆蓋索引可以使得你的SQL語句不須要回表,僅僅訪問索引就可以獲得全部須要的數據,大大提升了查詢效率。
索引無關優化
-
不使用
*
、儘可能不使用union
,union all
等關鍵字、儘可能不使用or
關鍵字、儘可能使用等值判斷。 -
錶鏈接建議不超過 5 個。若是超過 5 個,則考慮表格的設計。(互聯網應用中)
-
錶鏈接方式使用外聯優於內聯。
-
外鏈接有基礎數據存在。如:A left join B,基礎數據是 A。
-
A inner join B
,沒有基礎數據的,先使用笛卡爾積完成全鏈接,在根據鏈接條件獲得內鏈接結果集。 -
大數據量級的表格作分頁查詢時,若是頁碼數量過大,則使用子查詢配合完成分頁邏輯。
Select * from table limit 1000000, 10 Select * from table where id in (select pk from table limit 100000, 10)
索引也可能失效
並非全部索引對查詢都有效,SQL 是根據表中數據來進行查詢優化的,當索引列有大量數據重複時,SQL 查詢可能不會去利用索引,如一表中有字段 sex,male、female 幾乎各一半,那麼即便在 sex 上建了索引也對查詢效率起不了做用。
組合索引使用
在使用索引字段做爲條件時,若是該索引是複合索引,那麼必須使用到該索引中的第一個字段做爲條件時才能保證系統使用該索引,不然該索引將不會被使用,而且應儘量的讓字段順序與索引順序相一致。
索引優化總結
- 全值匹配牛逼
- 最佳左前綴法則,好比創建了一個聯合索引(a,b,c),那麼其實咱們可利用的索引就有(a), (a,b), (a,b,c)
- 不在索引列上作任何操做(計算、函數、(自動or手動)類型轉換),會致使索引失效而轉向全表掃描
- 存儲引擎不能使用索引中範圍條件右邊的列
- 儘可能使用覆蓋索引(只訪問索引的查詢(索引列和查詢列一致)),減小select
- is null ,is not null 也沒法使用索引
- like 「xxxx%」 是能夠用到索引的,like 「%xxxx」 則不行(like 「%xxx%」 同理)。like以通配符開頭(’%abc…’)索引失效會變成全表掃描的操做,
- 字符串不加單引號索引失效
- 少用or,用它來鏈接時會索引失效
- <,<=,=,>,>=,BETWEEN,IN 可用到索引,<>,not in ,!= 則不行,會致使全表掃描
其它注意事項
表格字段類型選擇
儘可能使用數字型字段,若只含數值信息的字段儘可能不要設計爲字符型,這會下降查詢和鏈接的性能,並會增長存儲開銷。這是由於引擎在處理查詢和鏈接時會逐個比較字符串中每個字符,而對於數字型而言只須要比較一次就夠了。
儘量的使用 varchar
代替char
,由於首先可變長度字段存儲空間小,能夠節省存儲空間,其次對於查詢來講,在一個相對較小的字段內搜索效率顯然要高些。
若是插入數據過多,考慮批量插入
當咱們插入數據過多時,一次性插入會嚴重影響性能,同時會形成卡頓,浪費時間,所以建議分批次插入數據。
反例:
for(User u :list){ INSERT into user(name,age) values(#name#,#age#) }
正例:
//一次500批量插入,分批進行 insert into user(name,age) values <foreach collection="list" item="item" index="index" separator=","> (#{item.name},#{item.age}) </foreach>
count(*) 和 count(1)和count(列名)區別
從執行效果上:
count(*)
包括了全部的列,至關於行數,在統計結果的時候,不會忽略列值爲NULLcount(1)
包括了全部列,用1表明代碼行,在統計結果的時候,不會忽略列值爲NULLcount(列名)
只包括列名那一列,在統計結果的時候,會忽略列值爲空(這裏的空不是隻空字符串或者0,而是表示null)的計數,即某個字段值爲NULL時,不統計。
執行效率上:
- 列名爲主鍵,
count(列名)
會比count(1)
快 - 列名不爲主鍵,
count(1)
會比count(列名)
快 - 若是表多個列而且沒有主鍵,則
count(1)
的執行效率優於count(*)
- 若是有主鍵,則
select count(主鍵)
的執行效率是最優的 - 若是表只有一個字段,則
select count(*)
最優。
有一個問題常常問:count(*)會形成全表掃描嗎?
你們能夠看如下這篇文章:【https://zhuanlan.zhihu.com/p/149874583?from_voters_page=true】
說明得很細。
參考: https://juejin.im/post/6850037271233331208#heading-65 https://www.jianshu.com/p/074f3eafcadf