【高性能MySQL】讀書摘錄5-第6章、查詢性能優化

第6章、查詢性能優化

1、慢查詢基礎:優化數據訪問

查詢性能低下的最基本的原因是訪問的數據太多,對於低效的查詢,可以從下面兩個步驟來分析:

(1)、確認應用程序是否在檢索大量超過需要的行,這通常意味着訪問了太多的行,但有時候也有可能訪問了太多的列。

(2)、確認MySQL服務器層是否在分析大量超過需要的數據行。

一些典型的情況:

(1)      查詢不需要的列。這樣的查詢上應該加上LIMIT

(2)      多表關聯時返回了全部列。應該只取需要的列。

(3)      總是取出全部的列:SELECT *

(4)      重複查詢需要的數據。較好的解決方案是使用數據緩存。

確認MySQL只返回了需要的數據之後,接下來應該看看查詢是否掃描了過多的數據,最簡單的衡量查詢開銷的三個指標如下:

(1)、響應時間

(2)、掃描的行數

(3)、返回的行數

響應時間=排隊時間+服務時間

理性情況下的掃描的行數和返回的行數應該是相等的。

訪問類型:MySQL有好幾種方式可以查找並返回一行結果,Explain的type列反應了訪問類型,訪問類型有全表掃描、索引掃描、範圍掃描、唯一索引查詢、常數引用等,這些速度從慢到塊,掃描的行數從大到小。Using Where表示MySQL將通過Where條件來篩選存儲引擎返回的數據。MySQL能夠以三種方式應用Where條件,從好到壞依次是:

(1)、在索引中使用Where來過濾數據,這是在存儲引擎層實現的

(2)、使用了索引覆蓋掃描(Extra列中Using index)來返回記錄,直接從索引中過濾不需要的記錄並返回命中的結果,這是在MySQL服務器層實現的,但是無需回表查詢記錄。

(3)、從數據表中返回數據,然後過濾不滿足條件的記錄,這是在MySQL服務器層實現的,MySQL需要先從數據庫中讀取記錄然後過濾。

如果一個查詢需要掃描大量的數據但是隻返回少數的行,那麼通常可以嘗試下面的技巧去優化:

(1)、使用索引覆蓋掃描,即把所有需要的列都放到索引中,這樣存儲引擎無需回表獲取對應行就可以返回結果了。

(2)、該表庫表結構,使用單獨的彙總表

(3)、重寫整個複雜的查詢,讓MySQL優化器能夠以最優化的方式執行整個查詢。

2、重構查詢的方式

確定一個複雜查詢還是多個簡單查詢更加有效

切分查詢:

將一個完整的查詢分散到多次小查詢中(例如通過Limit)

3、查詢執行的基礎

MySQL執行查詢的過程:

(1)、客戶端發送一條查詢給服務器

(2)、服務器先檢查查詢緩存,如果命中了緩存,則立即返回存儲在緩存中的結果,否則進入下一個階段。

(3)、服務器端進行SQL解析、預處理,再由優化器生成對應的執行計劃

(4)、將結果返回給客戶端。

MySQL客戶端、服務器端通信協議

         MySQL客戶端和服務器端的通信協議是“半雙工”的,在任何一個時刻,要麼是服務器向客戶端發送數據,要麼是由客戶端向服務器發送數據,這兩個動作不能同時發生

         當使用多數連接Mysql的庫函數從Mysql中獲取數據的時候,其結果看起來都像是從MySQL服務器獲取數據,實際上都是從這個庫函數的緩存中獲取數據。多數情況下,這沒有什麼問題,但是如果需要返回一個很大的數據集的時候,這樣做並不好,因爲庫函數會花費很多時間和內存來存儲所有的結果集,如果能夠儘早處理這些結果集,就能大大減少內存的消耗,這種情況下可以不適用緩存來處理記錄結果而是直接處理,這樣做的缺點是,對於服務器來說,需要查詢完成之後才能釋放資源,所以在和客戶端交互的過程中,服務器的資源都是被這個查詢所佔用的。

<?php

         $link= mysql_connect('', '', '');

         $result= mysql_query("SELECT * FROM table", $link);

        

         while($row = mysql_fetch_array( $result ) ){

                   //dosomething

         }

這段代碼看起來像是當你需要的時候,才循環從服務器端取出數據,而實際上,在上面的代碼中,在調用mysql_query()的時候,PHP就已經將整個結果集緩存到內存中了,而while循環僅僅是從這個緩存中逐行讀取數據。如果用Mysql_unbuffed_query代替mysql_query,則不會緩存結果。

查詢狀態

對於一個MySQL連接(一個線程),任何時刻都有一個狀態,該狀態表示了MySQL當前正在做什麼。可以用Show (full) processlist查詢。

Sleep : 線程正在等待客戶端發送新的請求

Query: 線程正在執行查詢或者將結果發送給客戶端

Locked: 在MySQL服務器層,該線程正在等待表鎖,在存儲引擎級別實現的鎖,例如InnoDB的行鎖,並不會體現在線程狀態中。

更多的信息可以查看MySQL的官方手冊。

查詢緩存:

在解析一個查詢語句之前,如果查詢緩存是打開的,那麼MySQL會優先檢查這個查詢是否命中這個查詢緩存中的數據,這個檢查是通過一個對大小寫敏感的哈希查找實現的,查詢和查詢緩存中即使只有一個字節不同,也不會匹配緩存結果。

TODO: query cache的更多細節。

1、  查詢優化處理

語法解析器和預處理

         MySQL通過關鍵字將SQL語句進行解析,並生成一顆對應的“解析樹”,MySQL解析器將使用MySQL語法規則進行驗證和解析查詢(語法分析),預處理器則會根據一些MySQL規則進一步檢查解析樹是否合法(語義分析),之後會驗證權限。

查詢優化器:

優化器將查詢轉化爲執行計劃,優化器的作用是找到最好的執行計劃。

         MySQL使用基於成本的優化器,它嘗試預測一個查詢使用某種執行計劃的成本,並選擇其中成本最小的一個。通過通過查詢當前會話的last_query_cost的值來得知MySQL計算的當前查詢的成本。優化器在評估成本的時候並不考慮任何層面的緩存,它假設讀取任何數據都需要一次磁盤I/O

         MySQL可以處理的優化類型:

(1)      重新定義關聯表的順序

(2)      將外連接轉化爲內連接

(3)      使用等價變化規則

(4)      優化COUNT(), MIN()和MAX() :例如要查找一個最小值,可以查詢B-Tree索引的最左端的記錄,如果要查詢一個最大值,也只需要獲取B-Tree索引的最後一條記錄。

(5)      預估並轉化爲常數表達式

(6)      覆蓋索引掃描:當索引中的列包含了所有查詢中使用的列時,MySQL可以使用覆蓋索引返回需要的數據,而無需查詢對應的數據行。

(7)      子查詢優化。

(8)      提前終止查詢:當發現已經滿足查詢需求的時候,MySQL總是能夠立刻終止查詢,一個典型的例子就是當使用LIMIT子句的時候

(9)      等值傳播:如果兩個列通過等式關聯,那麼MySQL能夠把其中一個列的WHERE條件傳遞到另外一個列上。

(10)  列表IN的優化。在很多數據庫系統中,IN()完全等價於多個OR條件的子句,因爲這兩者是完全等價的。在MySQL中,會對IN列表中的數據進行排序,然後通過二分查找的方式確定列表中的值是否滿足條件,對於IN列表中有大量取值的時候,MySQL的處理速度將會更快。

MySQL中如何執行關聯查詢:

         當前MySQL關聯執行的策略很簡單:對任何關聯都執行嵌套循環關聯操作,現在一個表中循環取出單條數據,然後再嵌套到下一個表中尋找匹配的行,如此下去,直到找到所有表中匹配的行爲止,然後根據各個表中匹配的行,返回查詢中需要的各個列。

執行計劃

         對某個查詢執行EXPLAINEXTENDED後,再執行SHOW WARNINGS,就可以看到重構出的查詢。

關聯優化查詢器

         決定最佳的表連接的順序。可以用SELECT STRAIGHT_JOIN強制按照查詢的順序進行表關聯。

排序優化

         無論如何,排序都是一個成本很高的操作,所以從性能角度考慮,應該儘量避免排序或者儘可能避免對大量數據進行排序。當不能使用索引生成排序結果的時候,MySQL需要進行排序,如果數據小則在內存中排序,如果數據量大則需要使用磁盤排序,MySQL將這個過程統一稱爲文件排序。

         MySQL使用兩種排序算法:舊版本使用“二次傳輸排序”,新版本使用“單次傳輸排序”

(1)、兩次傳輸排序:

         讀取行指針和需要排序的字段,對其進行排序,然後根據排序結果去讀取所需要的數據行。這需要兩次數據傳輸,第二次讀取的時候,因爲是讀取的排序後的所有記錄,這會產生大量的隨機I/O,所以兩次數據傳輸的成本非常高。不過這樣做的優點是:排序的時候儘量存儲較少的數據,可以再內存中容納儘量多的行數進行排序

(2)、單次傳輸排序:

         先讀取需要的所有列,然後根據給定列進行排序,最後直接返回排序結果,因爲不需要從數據表中讀取兩次數據,對於I/O密集型的應用,這樣的效率高了不少。相比兩次數據傳輸排序,這個算法只需要一次順序I/O讀取所有的數據,而無需任何的隨機I/O

查詢執行引擎:

         查詢執行的最後一個階段時將結果返回給客戶端,即使客戶端不需要返回結果,MySQL依然會返回一個這個查詢的一些信息,如該查詢影響到的行數,如果查詢可以被緩存,那麼MySQL在這個階段也會將結果存放到查詢緩存中。MySQL將結果集返回是一個增量、逐步返回的過程。

2、 MySQL查詢優化器的侷限性

關聯子查詢、UNION的限制(無法將限制條件從外層下推到內層)、索引合併優化、等值傳遞、並行執行、哈希關聯、鬆散索引掃描,最大值和最小值優化。

 

3、  優化特定類型的查詢:

優化COUNT()的查詢

COUNT可以統計行數和特定列的數量,統計列數量的時候,不會包含NULL。沒有任何條件的COUNT(*)對於MyISAM引擎而言比較快(MYISAM會維護一個錶行數的變量)

在一條查詢中同時統計一個列不同值的數量:

SELECT SUM(IF(color='blue',1,0)) AS blue, SUM(IF(color='red',1,0))AS red FROM items.

也可以用COUNT而不是SUM()實現同樣的目的:

SELECT count(color='blue' OR NULL) AS blue, count(color='red'OR NULL) AS red FROM items;

或者去掉IF表達式:

SELECT SUM(color='blue') AS blue, SUM(color='red') AS redFROM items.

優化關聯查詢

(1)、確保ON或者Using子句的列上有索引。一般來說,除非有其他理由,否則只需要在關聯順序中的第二個表中的相應列上添加索引。

(2)、確保任何的GROUP和ORDER BY中的表示式只設計其中一個表中的列,這樣MySQL纔有可能使用索引來優化這個過程。

優化子查詢

儘可能使用關聯查詢替代,至少當前的MySQL版本是這樣。

優化Group BY和DISTINCT:

當無法使用索引的時候,MySQL使用兩種策略完成分組:使用臨時表或者文件排序

優化LIMIT分頁

使用延遲關聯優化LIMIT分頁,避免偏移量較大的時候的性能低下問題。

SELECT film_id, xxx FROM film ORDER BY title LIMIT 50, 5;

可以優化爲:

SELECT film_id FROM film JOIN (

           SELECT film_idFROM film ORDER BY title LIMIT 50,5

) AS lim USING(film_id);

如果預先知道了邊界,也可以通過邊界計算。

優化UNION操作:

MySQL總是通過創建臨時表的方式來執行UNION操作,經常需要通過手工將WHERE, LIMIT, ORDER BY等字句下推到UNION的各個子查詢中,以便於優化器充分利用這些條件進行優化。除非確實需要服務器消除重複的行,否則一定需要ALL選項,如果沒有ALL選項,MySQL會給臨時表加上DISTINCT選項,會導致對整個臨時表做唯一性檢查,這樣做的代價很高。實際上,即使有ALL選項,MySQL依然會使用臨時表存儲結果。

使用用戶自定義變量

特點:

(1)、使用自定義變量的查詢,無法使用查詢緩存。

(2)、不能在使用常量或者標誌符的地方使用自定義變量,例如表名、列名和Limit子句中。

(3)、用戶自定義變量的生命週期是在一次連接中有效,所以不能用他們來做連接間的通信

(4)、如果使用數據池或者持久化連接,則可以實現一定程度的交互

(5)、5.0之前的版本中,自定義變量是大小寫敏感的。

(6)、不能顯式地生命自定義變量的類型。如果希望變量是整形,初始化0,如果希望是浮點型,初始化爲0.0,如果希望是字符串,初始化爲'',MySQL的自定義變量是一個動態類型。

(7)、MySQL優化器可能會在某些場景下將這些變量優化掉。

(8)、賦值的順序和賦值的時間點並不總是固定的,這依賴於優化器的決定

(9)、賦值符號:=的優先級非常低

(10)、使用未定義變量不會產生任何語法錯誤。

.案例學習(TODO):

(1)、使用MySQL構建一個隊列

(2)、計算兩點之間的距離

(3)、使用用戶自定義函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章