1、MySQL查詢執行基礎mysql
1. MySQL查詢執行流程原理算法
<1> 客戶端發送一條查詢給服務器。sql
<2> 服務器先檢查查詢緩存,若是命中了緩存,則馬上返回存儲在緩存中的結果。不然進入下一階段。緩存
<3> 服務器進行SQL解析、預處理,再由優化器生成對應的執行計劃。性能優化
<4> MySQL根據優化器生成的執行計劃,調用存儲引擎的API來執行查詢。服務器
<5> MySQL將結果返回給客戶端,同時保存一份到查詢緩存中。函數
2. MySQL客戶端/服務器通訊協議性能
<1> 協議類型:半雙工。優化
<2> Mysql一般須要等全部的數據都已經發送給客戶端才能釋放這條查詢所佔用的資源。spa
<3> 在PHP函數中,mysql_query()會將整個查詢的結果集緩存到內存中,而mysql_unbuffered_query()則不會緩存結果,直接從mysql服務器獲取結果。當結果集很大時,使用後者能減小內存的消耗,但服務器的資源會被這個查詢佔用比較長的時間。
3. 查詢狀態
可使用命令來查詢mysql當前查詢的狀態:show full processlist。返回結果中的「State」鍵對應的值就表示查詢的狀態,主要有如下幾種:
<1> Sleep:線程正在等待客戶端發送新的請求。
<2> Query:線程正在執行查詢或正在將結果發送給客戶端。
<3> Locked:在MySQL服務器層,該線程正在等待表鎖。(在沒行鎖的引擎出現)
<4> Analyzing and statistics:線程正在收集存儲引擎的統計信息,並生成查詢的執行計劃。
<5> Copying to tmp [on disk]:線程正在執行查詢,而且將其結果集都複製到一個臨時表中,這種狀態要麼是在作group by操做,要麼是文件排序操做,或者是union操做。
<6> Sorting result:線程正在對結果集進行排序。
<7> Sending data:表示多種請況,線程可能在多個狀態之間傳送數據,或者在生成結果集,或者在向客戶端返回數據。
4. 查詢緩存
<1> 這個檢查是經過一個對大小寫敏感的哈希查找實現的。
<2> 命中查詢緩存以後,檢查用戶權限,直接從緩存中返回數據給客戶端,不須要解析查詢。
5. 查詢優化處理
語法解析器和預處理:
<1> 經過關鍵字對SQL語句進行解析,生成一棵「解析樹」。
<2> 解析器使用MySQL語法規則驗證和解析查詢(關鍵字是否正確...)。
<3> 預處理器根據一些MySQL規則進一步檢查解析樹是否合法(表、列是否存在...)。
<4> 預處理器驗證權限。
查詢優化器:
MySQL使用基於成本的優化器。它將嘗試預測一個查詢使用各類執行計劃時的成本,並選擇其中成本最小的一個。其中,成本是根據存儲引擎提供的數據和引擎的統計信息計算得來的,能夠經過查詢當前會話的Last_query_cost的值來得知MySQL計算的當前查詢的成本。優化器在評估成本的時候並不考慮任何層面的緩存,它假設讀取任何數據都需要一次磁盤I/O。有不少緣由會致使MySQL優化器選擇並非最優的執行計劃。MySQL能處理的優化類型:從新定義關聯表的順序、將外鏈接轉化成內鏈接、使用等價變換規則、優化count()、min()和max()、預估並轉化爲常數表達式、覆蓋索引掃描、子查詢優化、提早終止查詢、等值傳播、列表in()的比較等等。
數據和索引的統計信息:
統計信息由存儲引擎實現。Mysql查詢優化器在生成查詢的執行計劃時須要向存儲引擎獲取相應的統計信息。
MySQL如何執行關聯查詢:
<1> MySQL認爲任何一個查詢都是一次關聯,對任何關聯都執行嵌套循環關聯操做,從一個表開始一直嵌套循環、回溯完成全部表關聯。
<2> Union查詢:先將一系列的單個查詢結果放到一個臨時表中,而後再從新讀出臨時表數據來完成union查詢。(臨時表沒有任何索引)
<3> MySQL在執行子查詢的時候也是先將子查詢的結果放到一個臨時表中。
<4> 遇到右鏈接的時候mysql會將其改寫成等價的左鏈接。
<5> 將全部的查詢類型都轉換成相似的執行計劃。。
<6> 並非全部的查詢均可以經過嵌套循環和回溯的方式完成,例如全外鏈接,因此Mysql並不支持全外鏈接。
執行計劃:
對某個查詢執行explain extended後再執行show warnings就能夠看到重構出的查詢。由於mysql執行查詢採用的老是嵌套循環關聯操做,因此mysql的執行計劃老是一棵左側深度優先的樹。
關聯查詢優化器:
MySQL優化器最重要的一部分就是關聯查詢優化。關聯優化器經過評估多個表的不一樣關聯順序的成原本選擇一個代價最小的關聯順序,若是可能,優化器會遍歷每個表而後逐個作嵌套循環計算每一棵可能的執行計劃樹的成本,最後返回最優的一個執行計劃。可是當關聯表的數量比較多的時候,這樣作的成本過高,當須要關聯的表數量超過optimizer_search_depth參數值的時候,優化器會選擇使用「貪婪」搜索的方式查找「最優」關聯順序。有時候優化器給出的不是最優關聯順序,這時若是不但願關聯優化器改變表的關聯順序的話,可使用straight_join來強制表的鏈接順序。
排序優化:
<1> 排序是一個成本很高的操做,應儘量避免排序操做。
<2> Mysql的兩種排序算法:
兩次傳輸排序(舊版本使用):讀取行指針和須要排序的字段,對其進行排序,而後再根據排序結果讀取所須要的數據行。
單次傳輸排序(新版本使用):先讀取查詢所須要的全部列,而後在根據給定列的值進行排序,最後直接返回結果。
兩個算法各有優缺點,當查詢須要全部列的總長度不超過參數max_length_for_sort的值時,mysql使用單次傳輸排序。
<3> 若是關聯查詢須要排序,MySQL會分兩種狀況來處理文件排序:
若是order by子句中的全部列都來自關聯的第一個表,那麼MySQL在關聯處理第一個表的時候就進行文件排序(Explain結果Extra字段「Using filesort」),不然,MySQL都會先將關聯的結果存放到一個臨時表中,而後在全部的關聯都結束後再進行文件排序(Explain結果Extra字段「Using temporary;Using filesort」)。
2、MySQL查詢優化器的侷限性
1. 關聯子查詢
MySQL的子查詢實現很是糟糕,最糟糕的一類查詢是where條件中包含IN()的子查詢語句。MySQL會將相關的外層表壓到子查詢中進行關聯查詢。
包含In的子查詢優化:
<1> 使用group_concat()在in()中構造一個由逗號分隔的列表。
<2> 改寫成關聯查詢或使用exists代替。
2. Union的限制
有時,MySQL沒法將限制條件從外層「下推」到內層,使得本來可以限制部分返回結果的條件沒法應用到內層查詢的優化上。在union的各個子句中分別使用order by和limit,能夠減小臨時表中的數據,但想獲取正確的順序還需加上一個全局的order by和limit操做。
3. 索引合併優化
在5.0和更新的版本中,當where子句中包含多個複雜條件的時候,mysql可以訪問單個表的多個索引以合併和交叉過濾的方式來定位須要查找的行。
4. 等值傳遞
Mysql優化器會將In()列表複製應用到關聯的各個表中。
5. 並行執行
Mysql沒法利用多核特性來並行執行查詢。
6. 哈希關聯
能夠經過建立一個哈希索引來曲線實現哈希關聯。
7. 鬆散索引掃描
Mysql並不支持鬆散索引掃描。
8. 最大值和最小值優化
在須要取最大/最小值的字段上建立索引,而後在查詢語句中加入「use index」語句強制使用索引,當MySQL讀到第一條知足條件的記錄的時候就是咱們須要找的最大/最小值了。
優化示例:
優化前:
優化後:
9. 在同一張表上查詢和更新
MySQL不容許對同一張表同時進行查詢和更新。能夠經過使用生成表的形式來繞過這個限制,由於MySQL只會把這個表當成臨時表來處理。
優化示例:
優化前:
優化後:
10. 查詢優化器的提示
能夠在查詢語句中加入一些提示來控制查詢的執行計劃。
3、優化特定類型的查詢
1. 優化count()查詢
若是在count()的括號中指定了列或者列的表達式,那麼統計的是這個表達式有值的結果數(不包含NULL)。統計行數使用count(*)意義更清晰,性能也會更好。
MyISAM執行沒有任何where條件的count(*)很是快,由於能夠利用存儲引擎的特性直接得到這個值。若是mysql知道某個列col不可能爲null值會將count(col)表達式優化爲count(*)。能夠利用MyISAM的這個特性來優化加速一些特定條件的count()查詢。優化示例:
優化前(須要掃描不少的數據行):
優化後(將須要掃描的數據行減小到5之內):
(在查詢優化階段會將其中的子查詢直接當作一個常數來處理)
在對精確值要求不高的狀況下,能夠經過一些途徑取得近似值來達到優化查詢的目的:
<1> 使用explain出來的優化器估算的行數來替代count(*),不須要真正去執行查詢。
<2> 去除一些對總數影響很小的where條件。
<3> 刪除distinct約束避免文件排序。
<4> 更復雜的優化:使用匯總表、使用緩存系統等。
2. 優化關聯查詢
<1> 確保on或using子句中的列上有索引,只須要在關聯順序中的第二個表的相應列上建立索引。
<2> 確保任何的group by和order by中的表達式只涉及到一個表中的列,這樣MySQL纔有可能使用索引來優化這個過程。
<3> 當升級MySQL的時候須要注意,關聯語法、運算符優先級等其餘可能會發生變化的地方。
3. 優化子查詢
儘可能使用關聯查詢替代(並不絕對,若是使用的是MySQL5.6或更新版本或MariaDB的話)。
4. 優化group by和distinct
優化這兩種查詢最有效的方法是使用索引來優化。
當沒法使用索引的時候,group by使用臨時表或文件排序來作分組,能夠經過提示SQL_BIG_RESULT和SQL_SMALL_RESULT來讓優化器按照咱們但願的方式運行。若是須要對關聯查詢作分組,一般採用查找表的標識列分組的效率會比其餘列更高。但若是查詢語句沒法寫成在select中直接使用非分組列的形式或當前sql_mode禁止這樣作的話(ONLY_FULL_GROUP_BY的sql_mode會返回錯誤),可使用min(),max()函數來繞過這種限制。
若是沒有經過order by子句顯式指定排序列,當查詢使用group by子句的時候結果集會自動按照分組的字段進行排序,可以在其後面直接使用asc或desc關鍵字指定排序方向。若不須要進行排序,能夠加上order by null讓MySQL不在進行文件排序,提升查詢性能。
5. 優化group by width rollup
使用超級聚合的查詢不夠優化,能夠在from子句中嵌套使用子查詢來替代超級聚合,或者是經過一個臨時表存放中間數據,然後和臨時表執行union操做來獲得最終結果。但最好的辦法仍是儘量將width rollup功能轉移到應用程序中進行處理。
6. 優化limit分頁
在使用limit子句的查詢中,若是沒有對應字段的索引,當偏移量很大的時候,mysql須要查詢大量數據行可是隻返回一小部分數據,對這種狀況進行優化的方法有:
<1> 使用索引覆蓋掃描,而後再作一次關聯操做返回所需的列。
<2> 想辦法將limit查詢轉化爲已知位置的查詢。
7. 優化union查詢
Mysql老是經過建立並填充臨時表的方式來執行union查詢,常常須要手工將where、limit、order by等子句「下推」到union的各個子查詢中進行優化。除非確實須要服務器消除重複的行,不然必定要使用union all,否則mysql會給臨時表加上distinct選項,這會致使對整個臨時表的數據作惟一性檢查。
8.優化select *查詢
當使用select * from tbl_name語句進行查詢的時候,mysql服務器會先從數據表中解析出所有字段名稱,替換掉查詢語句中的"*",而後緩存解析替換以後的查詢語句,最後再將解析替換以後的查詢語句進行執行,而且之後遇到select * from tbl_name語句都會直接使用緩存中的包含數據表所有字段的語句進行查詢。因此使用"*"而不指定字段名稱有如下弊端:
(1)會增長SQL的解析成本;
(2)若是不是所有字段都有用的話,查詢非必需字段還會形成資源浪費甚至影響服務器性能;
(3)沒法利用索引覆蓋查詢,不利於查詢的性能優化;
(4)如果數據表結構修改以後還使用緩存中的語句進行查詢,還會發生字段映射錯誤問題。
9. 使用用戶自定義變量優化查詢
使用用戶自定義變量的查詢沒法使用查詢緩存,生命週期只在一個鏈接中有效,是一個動態類型,賦值符號:=優先級很低,賦值表達式應該使用括號。
優化排名語句:
(演過最多電影的前10名演員,使用子查詢生成一箇中間的臨時表來解決自定義變量賦值時間和咱們預料不一樣的問題)
避免重複查詢剛剛更新的數據:
統計更新和插入的數量:
編寫偷懶的union:
第一個子查詢做爲分支條件先執行,若是找到了匹配的行,則跳過第二個分支。
(將「熱」數據和「冷」數據分別放在兩個不一樣的表,使用union去查詢)
有時優化器會把變量看成一個編譯時常量對待而不是對其進行賦值,將函數放在相似least()這樣的函數中一般能夠避免這樣的問題。