利用 group by 的 Loose Index Scan 優化 sql

參考文章:MySQL優化GROUP BY-鬆散索引掃描與緊湊索引掃描

背景

最近壓測的時候發現有一條sql語句在查詢到幾w條數據的時候,查詢耗時達到了1s左右,達到了我們線上設置的timeout,需要優化一下。

sql作用

sql是爲了從訂單表(Order)查詢哪些店鋪、商圈有待指派的訂單,where條件中有5個字段來過濾

  • A 訂單狀態,用A=a1來判斷是否是新訂單。
  • B 調度類型,分商圈b1和店鋪b2調度。
  • C 調度標誌,用C&c1=c1來表示是否支持自動調度。
  • D 區分訂單類型,主要有立即單d1和預約單d2。
  • E 期望送達時間。預約單的時候,用戶會設定期望何時送來,我們系統會根據這個值向前推x分鐘來進行調度。

設計之初,因爲group by操作會使用臨時表來操作,增大查詢耗時和內存使用,所以不建議用group by來操作,所以語句採用如下形式:

SELECT 商圈id FROM Order_x WHERE AND  A = a1 AND  B = b1 AND  (C & c1) = c1 AND   (D = d1  OR (D = d2 AND E < 1536227354));

查到商圈id後,在代碼層面做去重操作,返回上層,告訴那些商圈是有待指派訂單的。

優化前耗時

37735 rows in set (0.96 sec)

優化過程

慢的原因

首先用explain來看下sql使用的索引情況

從結果可以看出,可能掃描的數據行數達到了8W多行,使用的索引過濾效果不好。其實你要的數據確實也是3w多條,索引過濾後的數據太多,在Mysql sendingdata操作時耗時自然很大。

然後和DBA一起討論,看看是否能加其他索引來優化這個查詢。

但是我們where條件的幾個字段,除了E(期望送達時間)能起到很好的區分效果外(但這個區分度太高了),其他幾個字段取值範圍都非常小,就比如A(訂單狀態)騎士就7,8種,B(調度類型)就2種,即使加上索引效果也不明顯。因爲查詢出來的數據還是可能會多達幾萬條,無法解決這個問題。

我們嘗試了給where語句中的字段加索引仍然無法解決問題後,陷入了僵局。問題不是出在了索引,而是數據量大,那如何減少查詢的數據量呢?

改變思路

嘗試加上distinct來直接返回有訂單的商圈id。sql語句變形如下:

SELECT distinct(商圈id) FROM Order_x WHERE AND  A = a1 AND  B = b1 AND  (C & c1) = c1 AND   (D = d1  OR (D = d2 AND E < 1536227354));

但是查詢耗時仍然很大:

292 rows in set (0.98 sec)

explain顯示,掃描行數不變,Extra信息變成了Using where; Using temporary,因爲distinct會使用臨時表,耗時反而增大了。然後一頓騷操作,把sql語句直接推到變成了

SELECT distinct(商圈id) FROM Order_x WHERE AND  A = a1;

意外的變快了,用explain一查原來用到group-by索引,這一發現讓我覺得group by的操作不是我認知裏面的就一定慢。

Using where; Using index for group-by

後來我搜索下這個Using index for group-by這個關鍵字找到了group byLoose Index Scan,原來我的語句擊中了這個策略。然後我認真的看了相關知識,把SQL語句變爲下面的方式,且新建了一個索引:

//新建了一個索引
create index A_B_C_D_index on Order_x (A,B,C,D)
SELECT distinct(商圈id) FROM Order_x WHERE AND  A = a1 AND  B = b1 AND  (C & c1) = c1 AND   (D = d1  OR D = d2) group by A,B,C,D;
297 rows in set (0.02 sec)

查詢效率提升到了20ms,問題解決。

爲何效率提升這麼大?

因爲用到了group by Loose Index Scan,但是要用這個東西,條件比較嚴苛,我剛開始在沒理解明白的情況下,構造好幾種SQL語句,都沒有正確走到這個策略。

  • group by 後面跟的字段,必須在同一個索引中最前面的連續位置,比如你的索引是(A,B,C,D),你如果是group by B,C,D,這種是不觸發Loose Index Scan,因爲你A是第一個,所以第一個字段必須是A,
  • select後面的顯示字段必須跟group by保持一致。
  • 查詢只能針對一個表。
  • group by 後面沒有的字段,where條件裏面該字段必須是常量,我理解的是必須是=號標識,要向進行or或者&運算,必須把改字段添加到group by後面。
  • 建索引時必須把where條件後面所有的字段都要加到索引裏面去。

總結

  • 對SQL相關的知識比較缺乏,準備補補。
  • 聽別人,不如自己試驗下,本次group by在常規來說,我們不應該採用,但實際調研結果反而比較好。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章