1. 程式人生 > >一次慢查詢語句的問題定位及解決方案

一次慢查詢語句的問題定位及解決方案

問題現象:

前幾天在專案上線過程中,發現有一個頁面無法正確獲取資料,經排查原來是介面呼叫超時,而最後發現是因為SQL查詢長達到20多秒而導致了問題的發生。

複雜SQL語句的構成:

類比的語句來描述當時的場景,可以表達如下:

SELECT * FROM a_table AS a 
LEFT JOIN b_table AS b ON a.id=b.id 
WHERE a.id IN (
    SELECT DISTINCT id FROM a_table 
    WHERE user_id IN (100,102,103) GROUP BY user_id HAVING count(id) > 3
)

1)關聯查詢  

從上面簡化的SQL語句,可以看出,首先進行的是關聯查詢。

2)子查詢    

3) 其次,是巢狀的子查詢。此子查詢是為了找出多個使用者共同擁有的組ID。所以語句中的“100,102,103”是根據場景來定的,並且需要和後面“count(id) > 3”的個數對應。簡單來說,就是找使用者交集的組ID。

耗時在哪?

假設現在a_table表的資料量為20W,而b_table的資料量為2000W。大家可以想一下,你覺得主要的耗時是在關聯查詢部分,還是在子查詢部分?

(思考空間。。。。)

(思考空間。。。。)

(思考空間。。。。)

問題定位

初步斷定,首先,對於只有一個使用者ID時,我會把上面的語句簡化成:

SELECT * FROM
a_table AS a LEFT JOIN b_table AS b ON a.id=b.id WHERE user_id IN (100)

所以,初步斷定應該是巢狀的子查詢部分佔用了大部分的時間。

再進一步驗證

既然定位到了是巢狀的子查詢語句的問題,那又要分為兩塊待排查的區域:是子查詢本身耗時大,還是巢狀而導致慢查詢?

結果很容易發現,當我把子查詢單獨在DB中執行時,是非常快的。所以排除。

剩下的不言而喻,20秒的慢查詢是巢狀引起的

但因為處於上線緊急的過程中,為了確保,我快速地驗證了我的結論:

1、將子查詢的ID單獨執行,並把得到的結果序列手動拼成一段ID,如:1,2,3,4, ... , 999

2、將上面得到的序列ID,手動替換到原來的SQL語句

3、執行,發現,很快!只用了約150 ms

Well Done! 準備修復上線!

解決方案

線上的問題,很多時間都是在定位問題和分析原因,既然問題找到了,原因也找到了,解決方案不言而喻。程式碼簡單處理即可。

另外一個需要注意的點

因為b_table比a_table大,所以一開始 b_table 左關聯 a_table 時,很慢,大概是1秒多,而且資料量是很少的;但若反過來,a_table 左關聯 b_table 時,則很快,大概是100毫秒。

所以,又發現一個有趣的現象:

大表 左關聯 小表,很慢;小表 左關聯 大表,很快。

當然,這些我們理論上都知道,但實際開發會忘卻。又或者一開始兩個表都為空時,而又沒考慮到後期這兩個表增長的速度時,日後就會埋下坑了。

總結

巢狀的子查詢是很慢的。

大表左關聯小表很慢,小表左關聯大表,很快