背景
最近壓測的時候發現有一條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 by
的Loose 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
在常規來說,我們不應該採用,但實際調研結果反而比較好。