1. 程式人生 > 資料庫 >MySQL效能優化(五):為什麼查詢速度這麼慢

MySQL效能優化(五):為什麼查詢速度這麼慢

前面章節我們介紹瞭如何選擇優化的資料型別、如何高效的使用索引,這些對於高效能的MySQL來說是必不可少的。但這些還完全不夠,還需要合理的設計查詢。如果查詢寫的很糟糕,即使表結構再合理、索引再合適,也是無法實現高效能的。

談到MySQL效能優化,查詢優化作為優化的源頭,它也是最能體現一個系統是否更快。本章以及接下來的幾章將會著重講解關於查詢效能優化的內容,從中會介紹一些查詢優化的技巧,幫助大家更深刻地理解MySQL如何真正地執行查詢、究竟慢在哪裡、如何讓其快起來,並明白高效和低效的原因何在,這樣更有助於你更好的來優化查詢SQL語句。

本章從“為什麼查詢速度這麼慢”開始談起,讓你能夠清楚的知道查詢可能會慢在哪些環節,這樣將有助於你更好的優化查詢,做到心中有數,高人一籌

一、慢在哪

真正衡量查詢速度的是響應時間。 如果把查詢看作是一個任務,那麼它是由一系列子任務組成的,每個任務都會消耗一定的時間。如果要優化查詢,實際上要優化其子任務,那麼消除其中一些子任務,那麼減少子任務的執行次數,要麼讓子任務執行的更快。

MySQL在執行查詢的時候,有哪些子任務,哪些子任務花費的時間最多?這就需要藉助一些工具,或者一些方法(如:執行計劃)對查詢進行剖析,來定位發現究竟慢在哪。

通常來說,查詢的生命週期大致大致可以按照順序來看:從客戶端到伺服器,然後在伺服器上進行解析,生成執行計劃,執行,並返回結果給客戶端。 其中,“執行”可以認為是整個生命週期中最重要的階段,這其中包括了大量為了檢索資料到儲存引擎的呼叫以及呼叫後的資料處理,包括排序、分組等。

在完成這些任務的時候,查詢需要在不同階段的不同地方花費時間,包括網路、CPU計算,生成統計資訊和執行計劃、鎖等待等操作,尤其是向底層儲存引擎檢索資料的呼叫操作,這些呼叫需要在記憶體操作、CPU操作,還可能會產生大量的上下文切換以及系統呼叫。

在上述這些操作中,都會消耗大量的時間,其中會存在一些不必要的額外操作,其中有些操作可能被額外地重複執行了很多次、某些操作執行的很慢等等。這也就是查詢真正可能慢的地方,優化查詢的目的就是減少和消除這些操作所花費的時間。

通過上面的分析,我們對查詢的過程有了整體的瞭解,能夠清楚的知道查詢可能在哪些地方會存在問題,最終導致整個查詢很慢,為實際查詢優化提供方向。

換言之,查詢優化可以從以下兩個角度來出發:

  • 減少子查詢次數

  • 減少額外、重複的操作

查詢效能低下常見的原因是訪問的資料太多。在資料量小的時候,查詢速度還不錯,一旦資料量上來,查詢速度將會發生鉅變,讓人抓狂、體驗極差。針對查詢優化方面,可以從以下方面進行排查:

  • 是否查詢了不需要的資料

  • 是否掃描了額外的記錄

二、是否查詢了不需要的資料

在實際查詢中很多時候,會查詢了實際需要的資料,然後這些多餘的資料會被應用程式丟棄。這對MySQL來說是額外的開銷,同時也會消耗應用伺服器的CPU和記憶體資源。

一些典型案例如下:

1. 查詢不需要的記錄

這是一個常見的錯誤,常常會誤以為MySQL只會返回需要的資料,實際上MySQL卻是先返回全部結果集再進行計算。

開發者習慣性的先使用SELECT語句查詢大量的結果,然後由應用查詢或者前端展示層再獲取前面的N行資料,例如,在新聞網站中查詢100條記錄,但是隻是在頁面上顯示前10條。

最有效的解決方法是需要多少記錄就查詢多少記錄,通常會在查詢後面加上LIMIT,即:分頁查詢。

2. 多表關聯時返回全部列

如果你想查詢所有在電影Academy Dinosaur中出現的演員,千萬不要按下面的方式來進行查詢:

select * fromt actor a
inner join film_actor fa.actorId = a.actorId
inner join film f f.filmId = fa.filmId
where fa.title = 'Academy Dinosaur';

這樣將會返回三張表的全部資料列,而實際需求是要查詢演員資訊,正確的寫法應該是:

select a.* fromt actor a
inner join film_actor fa.actorId = a.actorId
inner join film f f.filmId = fa.filmId
where fa.title = 'Academy Dinosaur';

3. 總是查詢出全部列

每次看到select *的時候一定要用異樣的目光來審視它,是不是真的需要返回全部資料列?

在大部分情況下,是不需要的。select *會導致進行全表掃描,會讓優化器無法完成索引掃描這類優化,過多的列還會為伺服器帶來額外的I/O、記憶體和CPU的消耗。即使真的需要查詢出全部列,應該逐個羅列出全部列而不是*

4. 重複查詢相同的資料

如果你不太留意,很容易出現這樣的錯誤:不斷地重複執行相同的查詢,然後每次都返回完全相同的資料。

例如,在使用者評論的地方需要查詢使用者頭像的URL,那麼使用者多次評論的時候,可能就會反覆來查詢這個資料。比較好處理方法是,在初次查詢的時候將這個資料快取起來,後續使用時直接從快取中取出。

三、是否掃描了額外的記錄

確定查詢只查詢了需要的資料以後,接下來應該看看查詢過程中是否掃描了過多的資料。對於MySQL,最簡單衡量查詢開銷的三個指標如下:

  • 響應時間

  • 掃描的行數

  • 返回的行數

沒有哪個指標能夠完全來衡量查詢的開銷,但它們能夠大致反映MySQL內部執行查詢時需要訪問多少資料,並可以大概推算出查詢執行的實際。這三個指標都會記錄到MySQL的慢日誌中,所以檢查慢日誌記錄是找出掃描行數過多查詢的辦法。

慢查詢:用於記錄在MySQL中響應時間超過閾值(long_query_time,預設10s)的語句,並會將慢查詢記錄到慢日誌中。可通過變數slow_query_long來開啟慢查詢,預設是關閉狀態,可以將慢日誌記錄到表slow_log或檔案中,以供檢查分析。

1. 響應時間

響應時間是兩個部分之和:服務時間和排隊時間。服務時間是指資料庫處理這個查詢真正花費了多長時間。排隊時間是指伺服器因為等待某些資源而沒有真正執行查詢的時間,可能是等待I/O操作,也可能是等待行鎖等等。

在不同型別的應用壓力下,響應時間並沒有什麼一致的規律或者公式。諸如儲存引擎的鎖(表鎖,行鎖),高併發資源競爭,硬體響應等諸多因素都會影響響應時間,所以,響應時間既可能是一個問題的結果也可能是一個問題的原因,不同案例情況不同。

當你看到一個查詢的響應時間的時候,首先需要問問自己,這個響應時間是否是一個合理的值。

2. 掃描的行數和返回的行數

在分析查詢時,檢視該查詢掃描的行數是非常有幫助的,在此之上也能夠分析是否掃描了額外的記錄。

對於找出那些糟糕查詢,這個指標可能還不夠完美,因為並不是所有行的訪問代價都是相同的。較短的行的訪問速度相當快,記憶體中的行也比磁碟中的行的訪問速度要快的多。

**理想的情況下,掃描的行數和返回的行數應該是相同的。**但實際上這種美事並不多,例如在做一個關聯查詢的時候,掃描的行數和對返回的行數的比率通常都很小,一般在1:110:1之間,不過有時候這個值也可能非常大。

3. 掃描的行數和訪問型別

在評估查詢開銷的時候,需要考慮一下從表中找到某一行資料的成本。MySQL有好幾種訪問方式可以查詢並返回一行結果。這些訪問方式可能需要訪問很多行才能返回一條結果,也有些訪問方式可能無需掃描就能返回結果。

在執行計劃EXPLAIN語句中的type列反映了訪問型別。訪問型別有很多種,從全表掃描到索引掃描,範圍掃描,唯一索引,常數索引等。這裡列的這些,速度是從慢到快,掃描的行數也是從多到少。

如果查詢沒有辦法找到合適的訪問型別,那麼解決的最好辦法通常就是增加一個合適的索引,這也是我們之前討論索引的問題。現在應該明白為什麼索引對於查詢優化如此重要了。索引讓MySQL以最高效,掃描行數最少的方式找到需要的記錄。

如果發現查詢掃描了大量的資料但只返回少數的行,通常可以嘗試下面的技巧去優化它:

  • 使用索引覆蓋掃描,把所有需要用的列都放到索引中,這樣儲存引擎無需回表獲取對應的行就可以返回結果了。

  • 優化表結構。例如使用單獨的彙總表來完成查詢。

  • 重寫複雜查詢,讓MySQL優化器能夠以更優化的方式執行這個查詢。


MySQL效能優化(一):MySQL架構與核心問題

MySQL效能優化(二):選擇優化的資料型別

MySQL效能優化(三):深入理解索引的這點事

MySQL效能優化(四):如何高效正確的使用索引

MySQL效能優化(五):為什麼查詢速度這麼慢

MySQL效能優化(六):常見優化SQL的技巧

MySQL效能優化(七):MySQL執行計劃,真的很重要