前言
在實際資料庫項目開發中,由于我們不知道實際查詢時資料庫裡發生了什麼,也不知道資料庫是如何掃描表、如何使用索引的,是以,我們能感覺到的就隻有SQL語句的執行時間。尤其在資料規模比較大的場景下,如何寫查詢、優化查詢、如何使用索引就顯得很重要了。
那麼,問題來了,在查詢前有沒有可能估計下查詢要掃描多少行、使用哪些索引呢?
答案是肯定的。以MySQL為例,MySQL通過explain指令輸出執行計劃,對要執行的查詢進行分析。
什麼是執行計劃呢?
簡單來說,就是SQL在資料庫中執行時的表現情況,通常用于SQL性能分析、優化等場景。
本文從MySQL的邏輯結構講解,過渡到MySQL的查詢過程,然後給出執行計劃的例子并重點介紹執行計劃的輸出參數,進而了解為什麼我們會選擇文中建議的方案。
MySQL邏輯架構
MySQL邏輯架構分為三層,如下圖。

- 用戶端
- 如,連接配接處理、授權認證、安全等功能
- 核心服務
- MySQL大多數核心服務均在這一層
- 包括查詢解析、分析、優化、緩存、内置函數(如,時間、數學、加密等)
- 所有的跨存儲引擎的功能也在這一層,如,存儲過程、觸發器、視圖等
- 存儲引擎
- 負責MySQL中的資料存儲和讀取
- 中間的服務層通過API與存儲引擎通信,這些API屏蔽了不同存儲引擎間的差異
重點解釋下查詢緩存:對于select語句,在解析查詢之前,伺服器會先檢查查詢緩存(Query Cache)。如果命中,伺服器便不再執行查詢解析、優化和執行的過程,而是直接傳回緩存中的結果集。
MySQL查詢過程
如果能搞清楚MySQL是如何優化和執行查詢的,對優化查詢一定會有幫助。很多查詢優化實際上就是遵循一些原則讓優化器能夠按期望的合理的方式運作。
下圖是MySQL執行一個查詢的過程。實際上每一步都比想象中的複雜,尤其優化器,更複雜也更難了解。本文隻給予簡單的介紹。
MySQL查詢過程如下:
- 用戶端将查詢發送到MySQL伺服器
- 伺服器先檢查查詢緩存,如果命中,立即傳回緩存中的結果;否則進入下一階段
- 伺服器對SQL進行解析、預處理,再由優化器生成對象的執行計劃
- MySQL根據優化器生成的執行計劃,調用存儲引擎API來執行查詢
- 伺服器将結果傳回給用戶端,同時緩存查詢結果
執行計劃
優化與執行
MySQL會解析查詢,并建立内部資料結構(解析樹),并對其進行各種優化,包括重寫查詢、決定表的讀取順序、選擇合适的索引等。
使用者可通過關鍵字提示(hint)優化器,進而影響優化器的決策過程。也可以通過通過優化器解釋(explain)優化過程的各個因素,使使用者知道資料庫是如何進行優化決策的,并提供一個參考基準,便于使用者重構查詢和資料庫表的schema、修改資料庫配置等,使查詢盡可能高效。
例子
看個例子。
mysql> explain select name, nickname, ctime from dt_user where city = 'shanghai' order by name;
+----+-------------+------------+-------+--------------------------+---------------+---------+--------+---------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+-------+--------------------------+---------------+---------+--------+---------+-----------------------+
| 1 | SIMPLE | dt_user | range | PRIMARY,idx_city_name | idx_city_name | 2945 | NULL | 55183 | Using index condition |
+----+-------------+------------+-------+--------------------------+---------------+---------+--------+---------+-----------------------+
1 row in set (0.00 sec)
這個執行計劃給出的資訊是,該查詢通過一個簡單的給定範圍的掃描,共掃描55183行,使用index condition條件在dt_user表中篩選出,掃描過程中使用PRIMARY和idx_city_name索引。
輸出參數
輸出各字段解釋如下。更詳細的資訊請參考
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html- id
- select查詢序列号
- id相同,執行順序由上至下;id不同,id值越大優先級越高,越先被執行
-
select_type
查詢資料的操作類型,有如下
-
table
顯示該行資料是關于哪張表
-
partitions
比對的分區
-
type
表的連接配接類型,其值、性能由高到底排列如下
前5種情況都是理想的索引的情況。通常優化至少到range級别,最好能優化到ref。
-
possible_keys
指出 MySQL 使用哪個索引在該表找到行記錄。如果該值為 NULL,說明沒有使用索引,可以建立索引提高性能
-
key
顯示 MySQL 實際使用的索引。如果為 NULL,則沒有使用索引查詢
-
key_len
表示索引中使用的位元組數,通過該列計算查詢中使用的索引的長度。在不損失精确性的情況下,長度越短越好顯示的是索引字段的最大長度,并非實際使用長度
-
ref
顯示該表的索引字段關聯了哪張表的哪個字段
-
rows
根據表統計資訊及選用情況,大緻估算出找到所需的記錄或所需讀取的行數,數值越小越好
-
filtered
傳回結果的行數占讀取行數的百分比,值越大越好
-
extra
包含不适合在其他列中顯示但十分重要的額外資訊。常見的值如下
小結
資料庫性能優化很多,本文隻簡單了介紹MySQL邏輯結構、查詢過程和執行計劃參數。根據執行計劃輸出的索引使用情況、掃描的行數可以預估查詢效率,幫助我們重構查詢、優化表結構或者索引,進而盡可能提供查詢效率。