歡迎關注公衆號【sharedCode】致力於主流中間件的源碼分析, 能夠直接與我聯繫java
事故現場
202-11-19 系統接收到大量的超時告警, 同時業務羣裏面也有不少客戶反饋服務不可用。mysql
開始排查
首先上grafana上面查看總體的服務狀態,算法
從圖中能夠看出一點問題來,CPU幾乎沒有波動, tomcat線程數急劇上升, 系統的ops急劇降低。 這種是比較典型的資源阻塞類問題,爲了印證這個想法,咱們再看下當時的系統的GC狀況sql
從上面的GC狀況下,咱們能夠看出來,GC仍是比較平穩的,總體的停頓市場也很少,平均在100ms如下,雖然不算好,可是確定不會形成系統有如此大的停頓緩存
服務器出現故障排查方法:tomcat
服務器出現故障,先看CPU,若是CPU持續高漲,那麼確定是服務內部出現了問題,這個時候能夠按照網上的常規解決方法,top -HP pid
查看耗CPU比較嚴重的線程,而後導出對應的線程棧信息,就 能夠根據實際的業務去分析了, 這種屬於比較直觀的服務器
比較隱晦的資源阻塞問題,此類問題分爲以下兩種:函數
- 比較好排查的,即便接口慢,好比接口調用耗費時間久的外部接口,有大量慢SQL,這些都會間接的致使總體吞吐量降低,最終致使tomcat線程池線程池耗盡
- 第二種就更加隱晦了,CPU,帶寬,流量,慢SQL,內存各方面都很正常,可是tomcat線程池直線上升,最終服務器資源耗盡。 這種狀況我以前有專門寫過一片文章。《tomcat線程池排查》 這種狀況能夠考慮使用jstack命令,導出堆棧信息,這裏推薦一款工具 gceasy , 能夠清晰的分析出線程的狀態分佈,能夠很好的知道線程都堵在什麼地方了。
經過上面的已知條件和咱們過往的經驗,基本上能夠斷定是有一些接口阻塞致使總體系統處理能力急劇降低。 首先想到的就是慢SQL。工具
登錄到阿里雲的RDS控制檯上,查看RDS的運行情況oop
果不其然,在那個時間段,CPU已經到了100%了,基本上能夠肯定是慢查詢的問題, 在慢查詢的控制檯上,立馬能夠看到當前系統阻塞的SQL。觸目驚心,真的不知道是哪一個兔崽子寫的SQL。
罪魁禍首就是這條SQL
SELECT o.* FROM `jm_order` o LEFT JOIN jm_order_unregistered_driver d ON o.number = d.orderNumber WHERE o.valid = 1 AND o.state = 1 AND o.driver_uid = 0 AND o.agents_uid = 0 AND d.driverPhone IS NULL AND o.is_push_regular_car = 0 AND o.is_lock = 2 AND ( o.from_date > '2020-11-18' OR ( o.from_date = '2020-11-18' AND o.from_day >= 16 )) AND o.type IN ( 11, 12)
經過explain
關鍵字查詢執行計劃
問題一目瞭然了,看我紅線框起來的地方,這個就是問題所在,咱們能夠分析下這個SQL,這個SQL
裏面有兩張表,使用了left join , 其餘的卻是沒什麼問題,看索引走向以及掃描函數,其實看上去都沒啥問題,不該該耗時這麼久。
可是看紅色框起來的部分Using where; Using join buffer (Block Nested Loop)
, 這句話什麼意思?
下面給你們講一下mysql在錶鏈接的時候使用的算法,同時也讓你們理解一下爲何有小表驅動大表
的說法
mysql表關聯算法
Simple Nested Loop算法
這個算法,屬於簡單嵌套循環, 說白了就是外層表的結果做爲第一層循環,內層表做爲第二層循環,而後就這樣硬幹,
for (Table t:table) { for(Join x:joinTable){ if(t==x){ //xxxx ,說明匹配到了數據 for(){ // 若是有三種表關聯的話。 } } } }
上面這種暴力關聯的方法,可想而知效率那是差的一逼,基本上mysql官方也不會使用這種方式的。
執行順序:
- 先遍歷table1
- 遍歷table獲得的結果,逐條遍歷table2
- 遍歷完table2以後呢,繼續逐條遍歷table3, 返回最終的結果
執行次數基本上是: table1 * table2 * table3
Block Nested-Loop
這種算法,就是本文中生產環境實際遇到的,mysql默認在沒有創建索引
上面使用的算法, 這種作法和簡單嵌套循環有一點不一樣,就是加了 緩存塊
, 減小了循環次數 , 將驅動表的數據緩存到 join Buffer
裏面去,而後拿Join Buffer
裏面的數據和內層關聯表進行匹配,
for (Table t:table) { // store t in Join buffer , // 當緩衝池滿了,執行匹配 if(Join Buffer is full){ for(Join x:joinTable){ if(t in Buffer){ //xxxx ,說明匹配到了數據 for(){ // 若是有三種表關聯的話。 } } clear join buffer // 清空緩存池 } } }
從這裏能夠看到,使用了緩存池的話,減小了不少次數,好比:驅動表100條數據,被驅動表50條數據,那麼若是沒有Join Buffer
的話,讀表次數:100 * 50, 加了Join buffer
以後,若是Join buffer
的大小能夠存儲50條數據,那麼讀表次數就是: 100/50 * 50 , 讀表次數減小了一個數量級的。
須要注意的是,只有在 連表鍵上沒有索引的時候會採用這種方式
, 也就是本文出現的狀況。
Index Nested-loop
索引嵌套循環,簡稱 INL, 說白了就是 連表鍵上有索引,就直接走索引去作嵌套查詢
, 下面我畫一張圖來解釋
驅動表獲得結果以後,是直接去索引樹上找對應的被驅動表的記錄,若是可使用覆蓋索引的話,那麼就不用再作回表了,這種狀況下,效率是至關高的。
得出以上結果,立馬給orderNumber
加上索引,走索引嵌套算法就能夠了, 系統立刻就恢復正常了。
看完上面的文字,這下你們明白了爲啥有小表驅動大表的說法嗎?
歡迎關注公衆號【sharedCode】致力於主流中間件的源碼分析, 能夠直接與我聯繫