MySQL邏輯架構
-
MySQL邏輯架構總體分爲三層,最上層爲客戶端層,並不是MySQL所獨有,諸如:鏈接處理、受權認證、安全等功能均在這一層處理。html
-
MySQL大多數核心服務均在中間這一層,包括查詢解析、分析、優化、緩存、內置函數(好比:時間、數學、加密等函數)。全部的跨存儲引擎的功能也在這一層實現:存儲過程、觸發器、視圖等。mysql
-
最下層爲存儲引擎,其負責MySQL中的數據存儲和提取。和Linux下的文件系統相似,每種存儲引擎都有其優點和劣勢。中間的服務層經過API與存儲引擎通訊,這些API接口屏蔽了不一樣存儲引擎間的差別。sql
MySQL查詢過程
語法解析和預處理
MySQL經過關鍵字將SQL語句進行解析,並生成一顆對應的解析樹。這個過程解析器主要經過語法規則來驗證和解析。好比SQL中是否使用了錯誤的關鍵字或者關鍵字的順序是否正確等等。預處理則會根據MySQL規則進一步檢查解析樹是否合法。好比檢查要查詢的數據表和數據列是否存在等等。緩存
查詢優化
通過前面的步驟生成的語法樹被認爲是合法的了,而且由優化器將其轉化成查詢計劃。多數狀況下,一條查詢能夠有不少種執行方式,最後都返回相應的結果。優化器的做用就是找到這其中最好的執行計劃。安全
MySQL使用基於成本的優化器,它嘗試預測一個查詢使用某種執行計劃時的成本,並選擇其中成本最小的一個。在MySQL能夠經過查詢當前會話的last_query_cost的值來獲得其計算當前查詢的成本。服務器
mysql> select * from employees where first_name='Eric' limit 1; mysql> show status like 'last_query_cost'; +-----------------+--------------+ | Variable_name | Value | +-----------------+--------------+ | Last_query_cost | 60795.999000 | +-----------------+--------------+ 1 row in set (0.00 sec) mysql> select * from employees where emp_no=10226; mysql> show status like 'last_query_cost'; +-----------------+----------+ | Variable_name | Value | +-----------------+----------+ | Last_query_cost | 1.000000 | +-----------------+----------+ 1 row in set (0.00 sec)
示例中的第一個結果表示優化器認爲大概須要作60795.999個數據頁(?)的隨機查找才能完成上面的查詢。這個結果是根據一些列的統計信息計算得來的,這些統計信息包括:每張表或者索引的頁面個數、索引的基數、索引和數據行的長度、索引的分佈狀況等等。session
The total cost of the last compiled query as computed by the query optimizer. This is useful for comparing the cost of different query plans for the same query. The default value of 0 means that no query has been compiled yet. The default value is 0. Last_query_cost has session scope.架構
有很是多的緣由會致使MySQL選擇錯誤的執行計劃,好比統計信息不許確、不會考慮不受其控制的操做成本(用戶自定義函數、存儲過程)、MySQL認爲的最優跟咱們想的不同(咱們但願執行時間儘量短,但MySQL值選擇它認爲成本小的,但成本小並不意味着執行時間短)等等。函數
查詢執行引擎
在完成解析和優化階段之後,MySQL會生成對應的執行計劃,查詢執行引擎根據執行計劃給出的指令逐步執行得出結果。整個執行過程的大部分操做均是經過調用存儲引擎實現的接口來完成,這些接口被稱爲handler API。查詢過程當中的每一張表由一個handler實例表示。實際上,MySQL在查詢優化階段就爲每一張表建立了一個handler實例,優化器能夠根據這些實例的接口來獲取表的相關信息,包括表的全部列名、索引統計信息等。存儲引擎接口提供了很是豐富的功能,但其底層僅有幾十個接口,這些接口像搭積木同樣完成了一次查詢的大部分操做。性能
返回結果給客戶端
查詢執行的最後一個階段就是將結果返回給客戶端。即便查詢不到數據,MySQL仍然會返回這個查詢的相關信息,好比改查詢影響到的行數以及執行時間等等。
回頭總結一下MySQL整個查詢執行過程,總的來講分爲6個步驟:
-
客戶端向MySQL服務器發送一條查詢請求
-
服務器首先檢查查詢緩存,若是命中緩存,則馬上返回存儲在緩存中的結果。不然進入下一階段
-
服務器進行SQL解析、預處理、再由優化器生成對應的執行計劃
-
MySQL根據執行計劃,調用存儲引擎的API來執行查詢
-
將結果返回給客戶端,同時緩存查詢結果
查詢語句優化
-
MySQL不會使用索引的狀況:非獨立的列
mysql> explain SELECT * FROM employees.employees where emp_no=10226; +----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+ | 1 | SIMPLE | employees | const | PRIMARY | PRIMARY | 4 | const | 1 | NULL | +----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+ 1 row in set (0.00 sec) mysql> explain SELECT * FROM employees.employees where emp_no+1=10227; +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ | 1 | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL | 299335 | Using where | +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ 1 row in set (0.00 sec)
2.前綴索引
若是列很長,一般能夠索引開始的部分字符,這樣能夠有效節約索引空間,從而提升索引效率。
mysql> EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido'; +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ | 1 | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL | 299335 | Using where | +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ 1 row in set (0.00 sec)
根據選擇性創建索引:
mysql> SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.0042 | +-------------+ 1 row in set (0.14 sec) mysql> SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.9313 | +-------------+ 1 row in set (0.32 sec) mysql> SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.7879 | +-------------+ 1 row in set (0.29 sec) mysql> SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.9007 | +-------------+ 1 row in set (0.29 sec)
增長前綴索引:
mysql> ALTER TABLE employees.employees ADD INDEX `first_name_last_name4` (first_name, last_name(4)); Query OK, 0 rows affected (0.61 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido'; +----+-------------+-----------+------+-----------------------+-----------------------+---------+-------------+------+------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+-----------------------+-----------------------+---------+-------------+------+------------------------------------+ | 1 | SIMPLE | employees | ref | first_name_last_name4 | first_name_last_name4 | 22 | const,const | 1 | Using index condition; Using where | +----+-------------+-----------+------+-----------------------+-----------------------+---------+-------------+------+------------------------------------+ 1 row in set (0.00 sec)
3.多列索引
在多數狀況下,在多個列上創建獨立的索引並不能提升查詢性能。理由很是簡單,MySQL不知道選擇哪一個索引的查詢效率更好,因此在老版本,好比MySQL5.0以前就會隨便選擇一個列的索引,而新的版本會採用合併索引的策略。
mysql> explain SELECT * FROM employees.employees where emp_no < 10020 or first_name='Saniya'; +----+-------------+-----------+-------------+-------------------------------+-------------------------------+---------+------+------+--------------------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+-------------+-------------------------------+-------------------------------+---------+------+------+--------------------------------------------------------------+ | 1 | SIMPLE | employees | index_merge | PRIMARY,first_name_last_name4 | first_name_last_name4,PRIMARY | 16,4 | NULL | 275 | Using sort_union(first_name_last_name4,PRIMARY); Using where | +----+-------------+-----------+-------------+-------------------------------+-------------------------------+---------+------+------+--------------------------------------------------------------+ 1 row in set (0.00 sec)
4.覆蓋索引
當發起一個索引覆蓋的查詢時,在EXPLAIN的Extra列能夠看到「Using index」的信息。
mysql> explain select emp_no,first_name from employees where first_name = 'Saniya'; +----+-------------+-----------+------+-----------------------+-----------------------+---------+-------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+-----------------------+-----------------------+---------+-------+------+--------------------------+ | 1 | SIMPLE | employees | ref | first_name_last_name4 | first_name_last_name4 | 16 | const | 256 | Using where; Using index | +----+-------------+-----------+------+-----------------------+-----------------------+---------+-------+------+--------------------------+ 1 row in set (0.00 sec)
5.使用索引掃描來排序
MySQL有兩種方式能夠生產有序的結果集,其一是對結果集進行排序的操做,其二是按照索引順序掃描得出的結果天然是有序的。若是explain的結果中type列的值爲index表示使用了索引掃描來作排序。
在設計索引時,若是一個索引既可以知足排序,又知足查詢,是最好的。
只有當索引的列順序和ORDER BY子句的順序徹底一致,而且全部列的排序方向也同樣時,纔可以使用索引來對結果作排序。若是查詢須要關聯多張表,則只有ORDER BY子句引用的字段所有爲第一張表時,才能使用索引作排序。ORDER BY子句和查詢的限制是同樣的,都要知足最左前綴的要求(有一種狀況例外,就是最左的列被指定爲常數),其餘狀況下都須要執行排序操做,而沒法利用索引排序。
6.優化LIMIT分頁
當須要分頁操做時,一般會使用LIMIT加上偏移量的辦法實現,同時加上合適的ORDER BY字句。若是有對應的索引,一般效率會不錯,不然,MySQL須要作大量的文件排序操做。
一個常見的問題是當偏移量很是大的時候,好比:LIMIT 10000 20這樣的查詢,MySQL須要查詢10020條記錄而後只返回20條記錄,前面的10000條都將被拋棄,這樣的代價很是高。
優化這種查詢一個最簡單的辦法就是儘量的使用覆蓋索引掃描,而不是查詢全部的列。而後根據須要作一次關聯查詢再返回全部的列。對於偏移量很大時,這樣作的效率會提高很是大。
有時候若是可使用書籤記錄上次取數據的位置,那麼下次就能夠直接從該書籤記錄的位置開始掃描,這樣就能夠避免使用OFFSET,好比下面的查詢:
SELECT id FROM t LIMIT 10000, 10; 改成: SELECT id FROM t WHERE id > 10000 LIMIT 10;
參考資料:
1.我必須得告訴你們的MySQL優化原理
https://www.jianshu.com/p/d7665192aaaf
3.MySQL服務與存儲引擎間的接口
https://blog.csdn.net/itopcat/article/details/73614101
4.Writing a Custom Storage Engine