一個20秒SQL慢查詢優化的經歷與處理方案

背景

前幾天在項目上線過程當中,發現有一個頁面沒法正確獲取數據,經排查原來是接口調用超時,而最後發現是由於SQL查詢長達到20多秒而致使了問題的發生。sql

這裏,沒有高深的理論或技術,只是備忘一下經歷和解讀一些思想誤區。框架


複雜SQL語句的構成

這裏不過多對業務功能進行描述,但爲了突出問題所在,會用類比的語句來描述當時的場景。複雜的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
)

關聯查詢

從上面簡化的SQL語句,能夠看出,首先進行的是關聯查詢。
測試

子查詢

其次,是嵌套的子查詢。此子查詢是爲了找出多個用戶共同擁有的組ID。因此語句中的「100,102,103」是根據場景來定的,而且須要和後面「count(id) > 3」的個數對應。簡單來講,就是找用戶交集的組ID。spa


耗時在哪?

假設如今a_table表的數據量爲20W,而b_table的數據量爲2000W。你們能夠想一下,你以爲主要的耗時是在關聯查詢部分,仍是在子查詢部分?code


(思考空間。。。。)接口


(思考空間。。。。 。。。)
開發


(思考空間。。。。 。。。 。。。)
table


問題定位

對於SQL底層的原理和高深的理論,我暫時掌握不夠深刻。但我知道能夠經過類比和簡單的測試來驗證是哪一塊環節出了問題。class


初步判定

首先,對於只有一個用戶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秒的慢查詢是嵌套引發的


但由於處於上線緊急的過程當中,爲了確保,我快速地驗證了個人結論:

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

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

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


Well Done!  準備修復上線!


解決方案

線上的問題,不少時間都是在定位問題和分析緣由,既然問題找到了,緣由也找到了,解決方案不言而喻。代碼簡單處理便可。


另一個須要注意的點

當前,實際的SQL語句,會比這個更爲複雜,但已足以表達問題所在。但在前期,筆者也作了一些SQL的代碼。

由於b_table比a_table大,因此一開始 b_table 左關聯 a_table 時,很慢,大概是1秒多,並且數據量是不多的;但若反過來,a_table 左關聯 b_table 時,則很快,大概是100毫秒

因此,又發現一個有趣的現象:

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


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


總結

首先,嵌套的子查詢是很慢的。

緣由,我還沒仔細去研究,但在下班的路上和個人同事交流時,他說曾經看過這方面相關的書籍,是說每一次的子查詢都會產生一個SQL語句,因此就N次查詢了。而另一位資深的QA同事則跟我說,應該是M*N的問題。


其次,我一開始使用嵌套子查詢,是存在這樣一個誤區:我以爲將這些操做交給MySQL自身來處理會更高效,畢竟DB內部會有良好的機制來執行這些查詢由。

而後,實際表白,我錯了。由於這不是簡單的合併MC批量查詢。


當咱們決定使用一些底層的技術時,只有當咱們理解透徹了,才能使用更爲恰當。而由於無知就判定工具、框架、底層無所不能時,每每就會中招。

相關文章
相關標籤/搜索