寫在開頭
MySQL 憑藉其不花錢
的特性,在我們項目開發中佔有很大一部分。雖然我們會用 sql語句來進行數據查詢,但是你有了解過 MySQL 是如何進行查詢的嗎?
在面試中,我們也經常被問到SQL 優化
的內容,加個索引是最簡單而且高效的一種方法。那麼你有了解過SQL爲什麼查詢速度會慢嗎
?
SQL爲什麼查詢速度會慢?
一個SQL查詢的生命週期,大致可以按照順序來看:從客戶端到服務端、在服務器上進行解析、生成執行計劃 、執行、並返回結果給客戶端。其中在執行
階段包含了大量的爲了檢索數據到存儲引擎的調用、以及調用後對數據的處理過程,包括排序、分組等。
查詢速度慢的原因在於:某些不必要的額外操作。比如:某些操作被額外的重複執行很多次,某些操作執行太慢。
優化查詢的目的,就是減少和消除這些操作所花費的時間。
接下來,我們就從 MySQL 底層,來了解一下它到底是怎麼一個執行流程。
MySQL 執行流程
MySQL 執行流程,分爲 5 步:
客戶端發送一條查詢給服務器;
MySQL 服務器端先檢查緩存。如果命中緩存,則直接返回結果;否則進入下一階段;
MySQL 服務器端進行 SQL 解析、預處理,再由優化器生成對應的執行計劃;
MySQL 根據優化器生成的執行計劃,調用存儲引擎的 API 來執行查詢;
將查詢結果返回客戶端。
下來我們就開始對 MySQL 執行的每一步進行詳細瞭解。
1.MySQL 客戶端/服務端通信
MySQL 客戶端與服務端的通信,採用的是"半雙工"
的方式。此處涉及到一個數據傳輸
的概念,數據傳輸共分爲單工、半雙工、全雙工 三種。
單工
:數據傳輸只能在在一個方向上傳輸,一方固定爲發送端,另一方固定爲接收端;舉例:電視機半雙工
:數據允許在兩個方向上傳輸。在某一時刻,只允許數據在一個方向上傳輸,即:同一時間只可以有一方接收或者發送消息,可以實現雙向通信。全雙工
:數據允許 同時 在兩個方向上傳輸。即:發送設備和接收設備都具有獨立的接收和發送能力,在同一時間可以同時接收和發送消息,實現雙向通信。距離:電話通信
查詢 MySQL客戶端/服務端連接狀態:
對於一個 MySQL 連接,或者說一個線程,時刻都有一個狀態來標識這個連接正在做的操作。我們可以通過命令:show full processlist / show processlist
來查看。
我們可以通過上面內容,我們可以來查看當前 MySQL 線程處於哪種狀態。Command 常用的線程命令值有以下幾種:
Sleep
線程正在等待客戶端發送數據Execute
線程正在執行準備好的語句Query
連接線程正在執行查詢
Command 全部線程命令值,可參考官網介紹:https://dev.mysql.com/doc/refman/5.7/en/thread-commands.html
常見的 State 狀態 有以下幾種:
Locked
線程正在等待表鎖的釋放 (MySQL 之前版本是 Locked,新版本鎖的粒度更細了)Sorting result
線程正在對結果進行排序Sending data
向請求段返回數據
日常開發中,如果某個連接被鎖住了,我們可通過
kill {id}
的方式將該連接殺掉。上面只介紹了常見的幾種狀態,全部狀態可參考官網介紹:(MySQL 5.7 版本)https://dev.mysql.com/doc/refman/5.7/en/general-thread-states.html
2.MySQL 查詢緩存
數據庫連接成功,這個時候就可以執行SQL語句了。MySQL 默認是關閉緩存的,如需使用緩存,需要我們手動開啓。下文介紹默認緩存已開啓。如何開啓緩存,請繼續往下讀。
執行語句時,MySQL 首先會去查詢緩存,查看之前有沒有執行過同樣的語句(同樣的語句指:SQL語句必須一模一樣,多一個空格,少一個空格都認爲不是一個同樣的語句)。
- 如果緩存不存在,MySQL 會將執行的
語句和結果
以key-value的形式存儲起來;(簡單理解爲 key=sql,value=結果集) - 再次查詢時,發現緩存存在,則不會進行後續的查詢流程,直接從緩存返回。
如何打開MySQL緩存:
修改配置文件,來開啓 MySQL 緩存:
vi /etc/my.cnf
,在 [mysqld]
中添加:
query_cache_size = 64M
query_cache_type = ON(兩個屬性需同時配置,緩存才能開啓生效)
然後,重啓 mysql 服務
service mysqld restart
通過命令:show variables like ‘query_cache%’;
來查看是否開啓緩存(此處爲單引號,顯示錯誤是CSDN問題)。
可以通過命令:show status like qcache%;
來查看緩存的相關情況
緩存失效問題:
MySQL 只要表中的數據發生變化,緩存便會失效。
比如:我們使用語句select * from user where id = 1
查詢,此時 Qcache_inserts
會加 1;當再次使用該語句查詢,此時Qcache_hits
命中數會加 1。
此時,我們使用語句update user set name=xxx where id = 5
修改表中數據,儘管修改的是 id = 5 的這條,和 id = 1 沒有任何關係,但是此時你再來 select * from user where id = 1
查詢時, Qcache_inserts
會加 1,說明緩存已經失效。
MySQL緩存缺點: 只要修改表中數據,儘管與已緩存的數據無關聯,但是所有緩存都會失效,這是一個不好的點。
什麼情況下,數據不會被緩存:
- 加上
SQL_NO_CACHE
參數將不緩存 - 有
now()、current_date() 等函數的查詢
,不會緩存(select *,now() from user; 這種是不會進入緩存的) - 查詢語句
不涉及表
,不會緩存(select version(); ----查詢MySQL版本號 ) - 查詢的語句是
系統表
,不會緩存(select * from mysql.user;) - 查詢的語句的結果,超過了
query_cache_limit
時,也不會緩存
MySQL默認關閉緩存原因?
- 在查詢之前,必須先檢查是否命中緩存,浪費計算資源;
- 如果查詢可以被緩存,name執行完成後,MySQL發現緩存中並沒有這條數據,則會將結果存入緩存,這將會帶來額外的系統消耗;
- 針對表的
寫入
或更新數據
時,對應表的所有緩存將都會失效; - 如果查詢緩存很大或者碎片很多時,這個操作可能帶來很大的系統消耗。
MySQL緩存適用場景:
適用於以讀爲主,數據生成之後就不常改變的業務。比如門戶類、新聞類、BI 報表類
、論壇類等。
3.MySQL 查詢優化處理階段
MySQL 的查詢優化處理,分爲以下三個階段:
- 解析SQL
通過 Lex 此法分析、Yacc語法分析,將 SQL 語句解析成解析樹
。想了解Yacc、Lex ,請參考:Yacc 與 Lex 快速入門 - 預處理階段
根據 MySQL 的語法規則,進一步檢查解析樹的合法性
。如:檢查數據的表和列是否存在,解析名字和別名的設置等,還會進行權限的相關驗證操作 - 查詢優化器階段
查詢優化器,可以將解析樹轉化爲執行計劃。一條查詢可以由很多種執行方式,最後都返回相同的結果。優化器的作用就是找到這其中最好的執行計劃。