合理的表結構、索引對于高性能查詢來說是必不可少的。但是還需要合理的設計查詢。如果查詢寫得很糟糕,那么表結構再合理、索引再合適,也無法實現高性能。
因此高性能的查詢依賴于這三點:查詢優化、索引優化、表結構優化。
為什么查詢速度會慢
如果把查詢看作是一個任務,那么它由一系列子任務組成,每個子任務都會消耗一定的時間。如果要優化查詢,實際上要優化其子任務,要么消除其中一些子任務,要么減少子任務的執行次數,要么讓子任務運行得更快。
查詢的生命周期:從客戶端,到服務器,然后在服務器上進行解析,生成執行計劃,執行(包括調用存儲引擎及調用后的排序、分組等數據處理),并返回結果給客戶端。
在完成這些任務時,查詢需要在不同地方花費時間,包括網絡,CPU 計算,生成統計信息和執行計劃、鎖等待等操作。
優化數據訪問
查詢性能低下的原因可能是以下兩種:
- 應用程序可能檢索了超過需要的數據(訪問了太多的行或者太多的列)。
- MySQL 服務器層可能在分析大量超過需要的數據行。
請求了不需要的數據
- 應用程序中做分頁查詢。
- 只查找確實需要的列,不用 SELECT *。
- 不要做重復查詢。
MySQL 在掃描額外的記錄
在確定了查詢只返回需要的數據以后,接下來應該看查詢為了返回結果是否掃描了過多的數據。最簡單的衡量查詢開銷的三個指標如下:
- 響應時間
- 掃描的行數
- 返回的行數
一般 MySQL 使用如下三種方式應用 WHERE 條件,從好到壞依次為:
- 在索引中使用 WHERE 條件來過濾不匹配的記錄。這是在存儲引擎層完成的。
- 使用覆蓋索引返回記錄,直接從索引中過濾不需要的記錄并返回命中的結果。這是在 MySQL 服務器層完成的,但無需再回表查詢記錄。
- 從數據表中返回數據,然后過濾不滿足條件的記錄。這是在 MySQL 服務器層完成的,MySQL 需要先從數據表讀出記錄然后過濾。
如果發現查詢需要掃描大量的數據但只返回少數的行,可以這么做:
- 使用覆蓋索引掃描
- 改變庫表結構。例如使用單獨的匯總表。
- 重寫復雜的查詢。
重構查詢的方式
一個復雜查詢還是多個簡單查詢
一般情況下,能用一個查詢解決的事就不要用多個查詢。因為每次查詢都有網絡通信、查詢解析和優化的過程。但是也有例外。因為 MySQL 從設計上讓連接和斷開連接都很輕量級,在返回一個小的查詢結果方面很高效。
切分查詢
比如需要將某張表的全部數據做DML操作,可以分多次進行。防止一次鎖住很多數據、占滿整個事務日志、耗盡系統資源、阻塞很多小的但重要的查詢。
分解關聯查詢
將一個關聯查詢分解為多個查詢的優勢:
- 讓緩存效率更高
- 減少鎖的競爭
- 便于對數據庫進行拆分
- 查詢本身效率可能會有所提高,如用 in()代替關聯查詢,可以讓MySQL 按照 ID 順序查詢,這可能比隨機的關聯要更高效。
- 可以減少冗余記錄的查詢。應用層做關聯查詢,意味著對于某條記錄應用只需要查詢一次,而在數據庫中做關聯查詢,則可能需要重復地訪問一部分數據。
查詢執行的基礎
MySQL 執行一個查詢的過程,到底做了些什么:
- 客戶端發送一條查詢給服務器。
- 服務器檢查查詢是否可以命中緩存,命中則直接返回。否則進入下一階段。
- 服務器進行 SQL 解析、預處理,再由優化器生成對應的執行計劃。
- 調用存儲引擎的 API 來執行查詢
- 返回結果給客戶端
客戶端/服務器通信協議
通過抓包軟件分析,可以看到 MySQL 客戶端和服務器之間是通過 TCP 和 mysql 協議進行通信的。具體細節這里就不講了,我也沒仔細看。目前我們只要大致理解通信協議是如何工作的就夠了。
- 通信協議是“半雙工”的:任意一個時刻,要么是服務器向客戶端發送數據,要么是客戶端向服務器發送數據。無法進行流量控制,一旦一端開始發送消息,另一端要接受完整消息才能響應它。
- max_allowed_packet 參數可以限制數據包的長度。
- 服務器響應給用戶的數據通常很多,由多個數據包組成。
- 客戶端接受數據一般是將全部結果集緩存到內存中。這樣可以減少服務器的壓力。
查詢緩存
在解析查詢語句之前,如果查詢緩存是打開的,那么 MySQL 會優先檢查這個查詢是否命中查詢緩存中的數據。沒命中則走下一個階段。命中了,在返回結果之前還會檢查用戶權限。如果權限沒問題,則直接返回數據給客戶端。這種情況下,查詢不會被解析,不用生成執行計劃,不會被執行。
查詢優化處理
查詢的生命周期下一步是將 SQL 轉化成執行計劃。然后再按照執行計劃和存儲引擎交互。這包括多個子階段:解析 SQL,預處理,優化執行計劃。一條查詢可能有多種執行方式,最后都返回相同的結果。優化器的作用就是找到其中最好的執行計劃。
查詢執行引擎
存儲引擎根據查詢計劃來完成查詢。
返回結果給客戶端
查詢執行的最后一個階段是返回結果給客戶端。即使客戶端不需要結果,也會返回這個查詢的一些信息,如該查詢影響到的行數。MySQL 將結果集返回給客戶端是一個增量、逐步返回的過程。好處是服務器不需要緩存太多的結果,客戶端也能第一時間獲得返回的結果。
優化特定類型的查詢
- COUNT()
- 關聯查詢
- 子查詢
- GROUP BY 和 DISTINCT
- LIMIT 分頁
- UNION