最近作壓測的時候,發現有個接口的壓測,線程數才20,而其餘接口的都相對而言比較大。當時第一反應多是本身壓測錯了,試了好幾回,發現壓測的結果都差很少就是20.當時沒沒有深究緣由。mysql
後來老大說有時間讓我看看緣由。而後我看了下這個接口,壓根沒什麼東西,就2條sql。用這兩條sql去查詢了一下,發現查詢時間過長。而後我把這2條sql貼到navicat裏面看了下,如下是我整個過程的分析:sql
原始的sql是這兩條:緩存
SELECT * FROM SMS_TEMPLATE_N ORDER BY TEMPLATE_TYPE=0 ASC, TEMPLATE_TYPE,TEMPLATE_ID;性能
SELECT SMS_TEMPLATE_ID,COUNT(*) FROM SMS_HISTORY_N WHERE SMS_STATUS = 1 AND SEND_TIME BETWEEN '2019-05-15 00:00:00' AND '2019-05-16 10:55:00' GROUP BY SMS_TEMPLATE_ID;優化
先一條一條的分析:線程
第一條:他的目的是查詢搜有的短信模板而後按模板類型升序排列,可是類型爲0時排在最後面。排序
當時感受這條sql沒什麼能夠優化的了。可是程序打印的時候很長。請嶽晨洋幫我看了下,他問我爲何要這樣寫,爲何要排序,而後看了下cms_boss的頁面這個版塊,和跳轉到這個頁面的部分,發現其實排不排序最後效果都能實現,而後徹底能夠把關於排序的去掉,因此sql改爲了這樣子:索引
SELECT * FROM SMS_TEMPLATE_N ORDER BY TEMPLATE_ID;接口
而後去模擬環境執行這條sql,執行時間只有1毫秒,我感受確實快了不少。開發
後來想了想,不對呀,我爲何會這樣寫呢?我對比了有參數查詢和沒有參數查詢的sql,在看了cms_boss的部分,確實是我排序的部分有重複的部分。而後我又想了想,不對呀,這2條sql的執行計劃是如出一轍的,不該該有這麼大的差異呀,我把原始的sql貼到模擬環境執行sql,看了下時間也是1毫秒,因此說,這條sql,除了有業務的重合,自己不是形成執行時間好久的緣由。
而後看第二條sql:
SELECT SMS_TEMPLATE_ID,COUNT(*) FROM SMS_HISTORY_N WHERE SMS_STATUS = 1 AND SEND_TIME BETWEEN '2019-05-15 00:00:00' AND '2019-05-16 10:55:00' GROUP BY SMS_TEMPLATE_ID;
這個是執行時間,而後在看一下執行計劃,
看到這個的時候,我當時的感受就是已經用到索引了呀,怎麼仍是這麼慢呢。
,百度了下type:
type,訪問類型,sql查詢優化中一個很重要的指標,結果值從好到壞依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
通常來講,好的sql查詢至少達到range級別,最好能達到ref
先備註下這些指標分別是什麼意思:
system :表只有一行記錄(等於系統表),這是const類型的特例,平時不會出現,能夠忽略不計
const:表示經過索引一次就找到了,const用於比較primary key 或者 unique索引。由於只需匹配一行數據,全部很快。
eq_ref:惟一性索引掃描,對於每一個索引鍵,表中只有一條記錄與之匹配。常見於主鍵 或 惟一索引掃描。
ref:非惟一性索引掃描,返回匹配某個單獨值的全部行。本質是也是一種索引訪問,它返回全部匹配某個單獨值的行,然而他可能會找到多個符合條件的行,因此它應該屬於查找和掃描的混合體
fulltext:全文搜索
ref_or_null:與ref相似,但包括NULL
index_merge:表示出現了索引合併優化(包括交集,並集以及交集之間的並集),但不包括跨表和全文索引。 這個比較複雜,目前的理解是合併單表的範圍索引掃描(若是成本估算比普通的range要更優的話)
unique_subquery:在in子查詢中,就是value in (select...)把形如「select unique_key_column」的子查詢替換。PS:因此不必定in子句中使用子查詢就是低效的!
index_subquery:同上,但把形如」select non_unique_key_column「的子查詢替換
range:只檢索給定範圍的行,使用一個索引來選擇行。key列顯示使用了那個索引。通常就是在where語句中出現了bettween、<、>、in等的查詢。這種索引列上的範圍掃描比全索引掃描要好。只須要開始於某個點,結束於另外一個點,不用掃描所有索引
index:Full Index Scan,index與ALL區別爲index類型只遍歷索引樹。(Index與ALL雖然都是讀全表,但index是從索引中讀取,而ALL是從硬盤讀取)
all:Full Table Scan,遍歷全表以找到匹配的行
我又看了下公司的規範,
看完這2條,我忽然意識到,看執行計劃,我好像少看了一個東西,extra,而後我找了一下關於extra的取值的意義:
Using filesort : mysql對數據使用一個外部的索引排序,而不是按照表內的索引進行排序讀取。也就是說mysql沒法利用索引完成的排序操做成爲「文件排序」
Using temporary: 使用臨時表保存中間結果,也就是說mysql在對查詢結果排序時使用了臨時表,常見於order by 和 group by
Using index: 表示相應的select操做中使用了覆蓋索引(Covering Index),避免了訪問表的數據行,效率高 。若是同時出現Using where,代表索引被用來執行索引鍵值的查找; 若是沒用同時出現Using where,代表索引用來讀取數據而非執行查找動做
覆蓋索引(Covering Index):也叫索引覆蓋。就是select列表中的字段,只用從索引中就能獲取,沒必要根據索引再次讀取數據文件,換句話說查詢列要被所建的索引覆蓋。
注意: a、如需使用覆蓋索引,select列表中的字段只取出須要的列,不要使用select *
b、若是將全部字段都建索引會致使索引文件過大,反而下降crud性能
Using where : 使用了where過濾
Using join buffer : 使用了連接緩存
Impossible WHERE: where子句的值老是false,不能用來獲取任何元祖
select tables optimized away: 在沒有group by子句的狀況下,基於索引優化MIN/MAX操做或者對於MyISAM存儲引擎優化COUNT(*)操做,沒必要等到執行階段在進行計算,查詢執行計劃生成的階段便可完成優化
distinct: 優化distinct操做,在找到第一個匹配的元祖後即中止找一樣值得動做
看完這個我仍是沒以爲這個有什麼問題存在的,而後我有從新看了下表結構,有一個聯合索引在的,仔細瞅了2眼,感受那個聯合索引有點怪,
我嘗試去掉了SMS_TEMPLATE_ID,而後從新執行了這條sql:
看到效果沒,立竿見影,快了不止一點點,而後我從新執行了下執行計劃:
而後回到剛剛看的type的解釋,上面又說到當範圍查找好比between的時候,通常type的類型就應該是range,我是在改出來效果以後纔想到這一點,真的是看東西只看了卻沒有好好吸取。這是很應該反思的一點。
這個語句用到了group by,而用到它的時候,通常extra會有Using temporary,改完以後的執行計劃,正好又驗證了這個結論。
效果雖然出來了,可是我仍是不知道爲何是這個結論,又瞅了眼這個sql和表結構,我試着把聯合索引加上SMS_TEMPLATE_ID,不過把他放在最後面,而後從新執行這個sql,而後看了下執行時間:
而後看了下執行計劃:
這個結果就已經很明朗了,致使最開始現象的緣由是索引創建的不合適,個人sql執行時,不符合最左前綴原則,因此纔會致使那種效果,既然加上SMS_TEMPLATE_ID放在聯合索引的最後面和不加它效果是同樣的,那索性就不加它了(後來證實這個想法是錯的)。
結果出來了以後,咱們再來回顧下,我看公司規範的時候,截的那個圖,order by的有序性,這一點一樣是適用於group by的,group by的字段是組合索引的一部分,應該放在索引組合順序的最後面。
那如今加不加SMS_TEMPLATE_ID,好像執行計劃,執行速度都看不出來什麼區別,那麼是否是就不加了?以個人個性來講,我是不會加的,後來把sql給到公司dba看,他給個人建議是這個索引是須要在最後面加上SMS_TEMPLATE_ID這個字段的,最直接的驗證效果就是拉大時間間距,多查幾天的,我試了下,確實多查幾天效果就變化很明顯,因此這個是應該加上的。也就是說有聯合索引且有group by的時候,聯合索引的最後面是要加上group by這個字段的。
這個結論出來以後,我把這個例子的答案跟朋友分享了下,他跟我說了一句:索引沒用好會有回表現象。
聽到這句話,第一反應眉頭緊鎖,額,我好像對回表現象沒有任何印象。百度了下回表現象:
朋友的解釋:經過二級索引查到主鍵索引,再經過主鍵索引查全表,時間複雜度到N方了
我截了一個定義,兩個解釋,重點看一下第二點:使用了索引,Extra中是using index & using where ,這個狀況下會回表查詢數據。
再看一眼一開始沒改的sql,它的執行計劃:
這不正是說明出現了回表查詢現象。這一點正好跟截圖的開發規範的第六條相呼應。
這個分析過程,我感受最深的就是多思考,不少東西,看了不少遍,可是它並非個人,我也沒有良好的吸取,每次都是須要的時候看看,這個明顯拉長了找問題的時間。
改完這個以後,我從新作了這個接口的壓測,線程數變成了180個:
top:
pinpoint:
memory:
tps:
cpu:
response time: