程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> MYSQL數據庫 >> MySQL綜合教程 >> 一次mysql慢查詢事故分析

一次mysql慢查詢事故分析

編輯:MySQL綜合教程

一次mysql慢查詢事故分析


年前項目組接微信公眾號。上線之後,跟微信相關的用cid列的查詢會話的SQL變慢了幾十倍!思考這個問題思考了很久,從出現以來一直是我心頭的一個結。cid這一列是建了索引的,普通的cid列更新都沒問題,為何只有微信的有問題?相同的前綴又是如何影響索引的?
分析過程 1.explain下微信cid的查詢,微信的cid會以mid-qqwanggou001為前綴插入數據
explain
select *
from analysis_sessions
where cid = "mid-qqwanggou001-b99359d9054171901c0"

分析結果如下:

\

從explain分析可以看出,這個查詢使用了索引,但是innodb認為有165萬行數據需要給mysql服務器篩選(也就是用where條件過濾)。如果這些龐大的數據在內存,遍歷一遍花不了多少時間。但是極有可能,這些數據是在磁盤上的。這麼多的數據從磁盤讀取然後載入內存,大量磁盤IO必然是十分的耗時的。
2.分析普通cid的查詢

取數據進行explain,cid = "sid-a2f9047ddf528d837e5f60843c83aae9"。這個數據是不帶公共前綴的。

explain
select *
from analysis_sessions
where cid = "sid-a2f9047ddf528d837e5f60843c83aae9"

 

分析結果如下:

\

相同的列,相同的索引,這次存儲引擎向mysql服務器僅僅返回了一行數據。也就是說innodb僅僅需要讀取一個二級索引的葉子節點。相對於上面那個sql的IO,壓力顯然小很多。
初步分析結論:帶有長前綴的cid查詢,innodb存儲引擎會向mysql上端服務器返回百萬級別的數據。這只是現象,我還是想問,相同的表,相同的列,相同的索引結構(B+樹索引),相同的查詢,僅僅不同的數據,結果為何有差麼大的差別?
近一步分析
糾結這個問題很久了,直到前天晚上散步時候,無意的會想到了 explain結果的key_len這一列。這一列我從來不看,覺得沒用,但是27與cid這一列50個varchar的定義格格不入。27明顯小於50,首先可以肯定,這個索引用的是前綴索引,說白了,截取了字符串的前面一部分作為索引數據。analysis_session表用的gbk編碼,也就是說,索引需要2個字節表示一個varchar。解釋一下key_len
27 = 2 * 12 + 2 + 1
27位的索引,僅僅索引了前面12個字符。中間的2存儲長度,後面的一個字節存儲Null信息,因為這一列是允許Null的。
最終結論:問題到這已經很明了了,微信cid的前綴是17個字符的,大於前綴索引的12個字符,也就是說,所有存儲微信cid數據(百萬級別)B+樹葉子節點將只有一個B+樹非葉節點的指針指向這裡。於是,當你查微信cid相關的數據時,所有微信cid將被返回給mysql服務器進行where過濾了,效率上講,這是很恐怖的。索引確實還是被用上了,不然會造成全表掃描。但是這個數據設計的有問題,B+樹的查找效率是O(LogN)的,但是遇上這個數據,立刻變成O(N),相當於一個局部全表掃描。
那麼合理的推測,只要有新增的微信cid,微信cid的查詢只會變的更慢!
引申,更佳的代碼 practice:
varchar,blob, text等邊長數據建索引的時候,數據庫會自動建前綴索引,於是B+樹不會索引整個字段的部分。很多同學喜歡用前綴作為字符串的標志,這次要注意了,有前車之鑒了。前綴存入mysql之後會降低檢索效率,前綴越長,B+樹查詢的效率越低。
這裡給出代碼的建議:
1.將前綴作為後綴,startWith改為endWith

2.不要嘗試後綴模糊搜索,like "%.com",這種做法更糟糕,完全用不了索引,於是全表掃描。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved