前言
在實際數據庫項目開發中,由於我們不知道實際查詢時數據庫裏發生了什麼,也不知道數據庫是如何掃描表、如何使用索引的,因此,我們能感知到的就只有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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
這個執行計劃給出的信息是,該查詢通過一個簡單的給定範圍的掃描,共掃描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
查詢數據的操作類型,有如下- simple
簡單查詢,不包含子查詢或union - primary
包含複雜的子查詢,最外層查詢標記爲該值 - subquery
在select或where裏包含子查詢,被標記爲該值 - derived
在from列表中包含的子查詢被標記爲該值,MySQL會遞歸執行這些子查詢,把結果放在臨時表 - union
若第二個select出現在union之後,則被標記爲該值
若union包含在from的子查詢中,外層select被標記爲derived - union result
從union表獲取結果的select
- simple
table
顯示該行數據是關於哪張表- partitions
匹配的分區 type
表的連接類型,其值、性能由高到底排列如下- system
表只有一行記錄,相當於系統表 - const
通過索引一次就找到,只匹配一行數據 - eq_ref
唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配
常用於主鍵或唯一索引掃描 - ref
非唯一性索引掃描,返回匹配某個單獨值的所有行
用於=、< 或 > 操作符帶索引的列 - range
只檢索給定範圍的行,使用一個索引來選擇行
一般使用between、>、< - index
只遍歷索引樹 - ALL
全表掃描,性能最差
前5種情況都是理想的索引的情況。通常優化至少到range級別,最好能優化到ref。
- system
possible_keys
指出 MySQL 使用哪個索引在該表找到行記錄。如果該值爲 NULL,說明沒有使用索引,可以建立索引提高性能- key
顯示 MySQL 實際使用的索引。如果爲 NULL,則沒有使用索引查詢 - key_len
表示索引中使用的字節數,通過該列計算查詢中使用的索引的長度。在不損失精確性的情況下,長度越短越好顯示的是索引字段的最大長度,並非實際使用長度 - ref
顯示該表的索引字段關聯了哪張表的哪個字段 - rows
根據表統計信息及選用情況,大致估算出找到所需的記錄或所需讀取的行數,數值越小越好 - filtered
返回結果的行數佔讀取行數的百分比,值越大越好 extra
包含不適合在其他列中顯示但十分重要的額外信息。常見的值如下- using filesort
MySQL會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取
若出現有該值,應該優化SQL語句 - using temporary
使用臨時表保存中間結果,比如,MySQL在對查詢結果排序時使用臨時表
常見於order by和group by
若出現有該值,應該優化SQL語句 - using index
表示select操作使用了覆蓋索引,避免了訪問表的數據行,效率不錯 - using where
where 子句用於限制哪一行 - using join buffer
使用連接緩存 - distinct
發現第一個匹配後,停止爲當前的行組合搜索更多的行
- using filesort
小結
數據庫性能優化很多,本文只簡單了介紹MySQL邏輯結構、查詢過程和執行計劃參數。根據執行計劃輸出的索引使用情況、掃描的行數可以預估查詢效率,幫助我們重構查詢、優化表結構或者索引,從而儘可能提供查詢效率。
Reference
- 《高性能MySQL》
- https://juejin.im/post/59d83f1651882545eb54fc7e
- https://blog.csdn.net/chenshun123/article/details/79677037
- https://yq.aliyun.com/articles/599674
- https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
- https://dev.mysql.com/doc/refman/5.7/en/explain-extended.html
- https://dev.mysql.com/doc/refman/5.7/en/estimating-performance.html
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
</div>