想要獲得更好的表現,你需要: 合理的表結構 + 出色的索引 + [不錯的查詢語句]
1. 慢查詢是怎麼回事
1.1 我怎麼才能知道我的查詢很慢
- 開啟慢查詢日誌,比如你認為查詢超過1秒就是慢,MySQL會記錄下超過1秒的查詢記錄
- 看看是不是因為MySQL任務太多導致你查詢很慢
- 使用EXPLAIN , 仔細研究每一條語句,看看是不是執行了不該執行的內容
1.2 你是不是向資料庫請求了多餘的資料
很多時候你其實在向資料庫請求了超多資源,可能你並沒有意識到,這些多餘的資料會被拋棄,並給MySQL伺服器端增加額外的壓力。 一些場景:
- 你取了所有資料,但是你的程式卻只用到前面10行:
- 如果你真的只需要10行,學會使用 LIMIT 10
- 取了全部的列,但是實際用不到。這可能會使你喪失使用覆蓋索引的機會
- 什麼是覆蓋索引? 假設你的索引是A+B,對應B-Tree內節點,有A+B的情況下可以查詢到完整資料儲存位置。 但是如果你 select A,B from users, 你直接讀B-Tree就完事了,你甚至都不用去找完整資料,更快。但是你還是選擇了 select * ,我們就需要回去找完整資料,去找你根本用不到的剩餘列
- 除了覆蓋索引,你還可能給伺服器帶來許多不必要的IO壓力
- 重複查詢的資料
- 比如查詢頭像這種請求,完全可以通過快取,不一定每次都要重新請求
1.3 MySQL有沒有掃描多餘的行
-- 使用索引
EXPLAIN SELECT * FROM users WHERE id = 1\G
********************** 1.row **********************
type: ref
key : id
rows: 10
-- 刪除索引
EXPLAIN SELECT * FROM users WHERE id = 1\G
********************** 1.row ***********************
type: ALL
rows: 5073
extra: Using Where
複製程式碼
我們可以通過"EXPLAIN" 命令看看這條命令是怎麼執行的,有沒有索引掃描內容真的差別太大
- 如果你有索引,並且通過索引檢索,我們使用 key=id 的 ref 方式,這種情況下,MySQL大概需要掃描10行,能得到你想要的資料
- 但是如果你沒有索引了,我們現在只能通過先全表掃描 + USING WHERE 的方式來篩選了,這種情況下預計得掃描5073行才能得到你想要的資料
- 真的掃描了太多本不需要的東西
在表面上,我們都直接在 SQL 語句加上WHERE就完事兒,並不過多的去關心效能問題,但是即使大家都是WHERE, 在"索引" 的輔助下也會存在很大的優劣之分
由好到差:
- WHERE 篩選項 即 索引, 這個在儲存引擎層就能完成 -> 圖一所示案例
- 索引能覆蓋掃描專案(使用覆蓋索引), 標誌是EXPLAIN顯示Extra=Using Index 操作手法為MySQL伺服器拿著索引前往B-Tree讀資料就結束,不用資料庫讀數
- 沒有使用索引,直接使用WHERE,資料庫引擎需要先從表中讀出資料,返回給MySQL伺服器,然後MySQL用WHERE過濾,這樣一來資料庫引擎一定掃描了很多資料 -> 圖二所示
總結一下,無論是方法1.儲存引擎能直接訪問需要的行,還是2.直接前往B-Tree讀數,都好過全表掃描,返回所有資料然後由MySQL做過濾。 為了達到這樣的效果,儘可能把要用到的WHERE篩選項放到索引中去
2. 查詢的過程是怎樣的
為了更好的做出優化,以及後面會提到的"快取命中",我們必須也要先知道查詢過程是怎樣的。 關於詳細的步驟,我們會在下面的環節描述一下各個部件是怎麼工作的
- 客戶端傳送一條請求給伺服器
- 伺服器先檢查快取,如果命中快取則直接返回結果,否則進入下一階段
- MySQL伺服器進行SQL解析,預處理,再由優化器生成執行計劃
- MySQL伺服器根據執行計劃呼叫儲存引擎API介面
- 將結果快取 並 返還給客戶端
2.1 客戶端 & MySQL 伺服器之間的互動
MySQL客戶端與MySQL伺服器之間的互動是半雙工的,也就是說同一時刻內只有其中一方向另一方傳送請求,這個請求可以是客戶端向伺服器傳送SQL語句,也可以是伺服器向客戶端返回所請求的資料,一旦客戶端傳送了請求以後,它所能做的就只有等待伺服器返回所請求的資料
- 等待伺服器查詢並返回的結果比較漫長,所以一個比較好的辦法是把查詢返回的資料進行快取,下次需要用的時候直接從快取讀取即可,可以減輕伺服器的壓力
- 當我們使用很多個連結傳送請求的時候,表面上看我們是從MySQL伺服器獲取資料,其實都是快取獲取資料,這樣可以很大程度提升效率
2.2 如何使用快取
在解析一個SQL語句之前伺服器會先看看是否有命中快取中的資料,也就是看看是否已經有快取上了。檢查的標準是通過對查詢語句的雜湊實現的,如果雜湊出的結果是一樣的就算命中,並且這個雜湊是對大小寫敏感的,也就是說哪怕是大小寫不一致都不能算命中
2.3 SQL解析 & 預處理
MySQL伺服器通過關鍵字將客戶端發來的SQL語句進行解析,解析器通過MYSQL語法對這條語句進行驗證,例如它將驗證是否使用了錯誤的關鍵字,關鍵字順序是否正確等。
前處理器則會去檢查它所請求的資料表以及列是否存在,並驗證許可權。
2.4 查詢優化器的優化原則
走到了這一步說明你的語句沒有問題,能執行,問題就是怎麼執行。 所以查詢優化器,會先找出很多個可能的做法,並嘗試找出最優解 優化原則? 找出成本最低的
mysql > SELECT SQL_NO_CACHE COUNT(*) FROM users
+----------+
| count(*) |
+----------+
| 5462 |
+----------+
mysql > SHOW STATUS LIKE 'Last_query_cost'
+-----------------+-------------+
| Variable_name | Value |
+-----------------+-------------+
| Last_query_cost | 1040.59 |
+-----------------+-------------+複製程式碼
- 你可以使用以上的方式檢視一下,上一條執行語句的 ”執行成本“ 是多少
- "執行成本" 說明MySQL伺服器認為需要載入1040個資料頁,並在其中做隨機查詢才可以
- 這個 "執行成本" 是這麼考量出來的
- 索引節點,涉及多少個頁面
- 索引以及資料行的長度,索引的分佈情況
- 但是優化器並不考慮有"快取" 這種東西,它假設每取一次數 == 一次磁碟IO
- "執行成本" 一定是準確的嗎?
- 一定不是,考量很多時候就無法考慮到全部情況,並且他也不知道什麼在記憶體裡什麼在磁碟上,在記憶體裡的都不用做磁碟IO
- 但是大部分時候計算出來的執行成本會比人思考的更準確
2.5 MySQL優化器 - 重新定義關聯順序
2.5.1 先簡單介紹一下聯表的執行過程 (如果你知道就可以跳過了)
-- 上圖反應的就是下面個SQL語句的聯表過程
SELECT tbl1.col1 , tbl2.col2
FROM tbl1 JOIN tbl2 USING(col3)
WHERE tbl1.col1 IN(5,6)複製程式碼
表之間的關聯遵循一種"巢狀" 的規則,用最簡單的話說就是先取表[tbl1]的第一行,去表[tbl2]中做匹配,對於我們,我們自然是希望執行的步驟越少越好:
- 我們先從[tbl1]中挑出 col1在(5,6)範圍內的所有記錄
- 對於每一條這樣的記錄,去遍歷 tbl2, 找出匹配,成為符合的輸出
- 所以,如果第一個表,它滿足條件的記錄,越少,是不是我們在tbl2中遍歷,也就越少?因此一個大原則誕生了,我們一定希望,第一張表符合條件的越少越好。 這個原則會成為聯表優化最重要的原則
2.5.2 聯表優化實戰分析
SELECT tbl1.col1, tbl2.col2, tbl3.col3
FROM tbl1
INNER JOIN tbl2 USING(tbl1.col1)
INNER JOIN tbl3 USING(tbl3.col3);複製程式碼
- tbl1 一共 1000行,tbl3 一共200行, 三張表都有索引
- 雖然第一個出現在SQL語句裡的tbl1, 在經過聯表優化以後第一個出現的是tbl3,原因:
- 如果我們拿著第一張表的索引前往第二張表匹配,根據索引查詢都很快
- 所以問題變成了如果第一張表會越短,我們匹配的次數就越少
- tbl3 比 tbl1 短,因此被自動優化成第一個出現的表