MySQL查詢優化器工作原理解析


手冊上查詢優化器概述

查詢優化器的任務是發現執行SQL查詢的最佳方案。大多數查詢優化器,包括MySQL的查詢優化器,總或多或少地在所有可能的查詢評估方案中搜索最佳方案。對於聯接查詢,MySQL優化器所調查的可能的方案數隨查詢中所引用的表的數目呈指數增長。對於小數量的表(典型小於7-10),這不是一個問題。然而,當提交的查詢更大時,查詢優化所花的時間會很容易地成為服務器性能的主要瓶頸。 
查詢優化的一個更加靈活的方法是允許用戶控制優化器詳盡地搜索最佳查詢評估方案。一般思想是優化器調查的方案越少,它編譯一個查詢所花費的時間越少。另一方面,因為優化器跳過了一些方案,它可能錯過一個最佳方案。 
優化器關於方案數量評估的行為可以通過兩個系統變量來控制:

  • optimizer_prune_level變量告訴優化器根據對每個表訪問的行數的估計跳過某些方案。我們的試驗顯示該類“有根據的猜測”很少錯過最佳方案,並且可以大大降低查詢編輯次數。這就是為什么默認情況該選項為on(optimizer_prune_level=1)。然而,如果你認為優化器錯過了一個更好的查詢方案,則該選項可以關閉(optimizer_prune_level=0),風險是查詢編輯花費的時間更長。請注意即使使用該啟發,優化器仍然可以探測呈指數數目的方案。

  • ptimizer_search_depth變量告訴優化器對於每個未完成的“未來的”方案,應查看多深,以評估是否應對它進一步擴大。optimizer_search_depth值較小會使查詢編輯次數大大減小。例如,如果optimizer_search_depth接近於查詢中表的數量,對12、13或更多表的查詢很可能需要幾小時甚至幾天的時間來編譯。同時,如果用optimizer_search_depth等於3或4編輯,對於同一個查詢,編譯器編譯時間可以少於1分鍾。如果不能確定合理的optimizer_search_depth值,該變量可以設置為0,告訴優化器自動確定該值。 
    我們可以通過show variables 來查看這些參數 
    這里寫圖片描述 
    備注(手冊網址:http://doc.mysql.cn/mysql5/refman-5.1-zh.html-chapter

個人理解

從官方手冊上看,可以理解為,MySQL采用了基於開銷的優化器,以確定處理查詢的最解方式,也就是說執行查詢之前,都會先選擇一條自以為最優的方案,然后執行這個方案來獲取結果。在很多情況下,MySQL能夠計算最佳的可能查詢計划,但在某些情況下,MySQL沒有關於數據的足夠信息,或者是提供太多的相關數據信息,估測就不那么友好了。 
但是感覺手冊上,並沒有說MySQL怎么去尋找最優方案呢? 
通過查詢相應的資料,個人理解如下 
MySQL優化器中,一個主要的目標是只要可能就是用索引,而且使用條件最嚴格的索引來盡可能多、盡可能快地排除那些不符合索引條件的數據行,說白了就是選擇怎樣使用索引,當然優化器還受其他的影響。為了更直觀,下面將通過例子來說明。 
創建一個表:

CREATE TABLE t8( id1 INT NOT NULL , id2 INT NOT NULL, KEY id1_key(`id1`), KEY id2_key(`id2`) ) ENGINE=MYISAM DEFAULT CHARSET=utf8; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

插入幾行數據如下: 
這里寫圖片描述 
當我執行如下查詢語句時候,查詢優化器會怎樣進行優化呢?

select * from t8 where id1=1 and id2=0;

當然,MySQL不會傻到,從t8表中的一行開始,然后一行行的去比較,id1與id2。優化器會先分析數據表,得知有索引id1_key與id2_key,如果先判斷id1_key的話,然后需要從4行數據中排除3行數據;如果先判斷id2_key的話,然后需要從2行中排除1行。對人來說,這兩種方式沒有什么區別,但是對於程序而言,先判斷id2_key需要較少的計算和磁盤輸入輸出。因此,查詢優化器會規定程序,先去檢驗id2_key索引,然后在從中挑出id2為0的數據行。 
通過下圖,我們可以看出,可以選擇的索引有id1_key與id2_key,但是實際用到的索引只有id2_key 
這里寫圖片描述 
如果將SQL語句改為 select * from t8 where id1=1 and id2=0;執行情況也是一樣的,不區分前后。如下圖: 
這里寫圖片描述

當然,如果將程序,修改為如下

select * from t8 where id1=5 and id2=0;

也可以分析得出,會使用id1_key索引 
這里寫圖片描述

當然,如果在創建一個復合索引

ALTER TABLE t8 ADD KEY id1_id2_key(`id1`,`id2`)

此時,在此執行select * from t8 where id1=1 and id2=0; 當然會考慮使用id1_id2_key索引。 
這里寫圖片描述 
通過上面的例子,可以理解查詢優化器在查詢的時候,是選擇哪一個索引作為最合適的索引。除此,也提示我們,要慎重選擇創建索引。如,上面創建了三個索引(id1_key、id1_key、id1_id2_key),但是優化器優化程序時候,每次只能從中選擇一個最合適的,如果創建過多,不僅僅是給數據的更新和插入帶來了壓力,同時也增加了優化器的壓力。

分析優化器優化過程中的信息

其實,在上面已經查看過優化器優化過程中的信息,無非就是使用explain。在這里,在集中說說,里面的參數意義。如下圖 
這里寫圖片描述 
id: MySQL Query Optimizer 選定的執行計划中查詢的序列號。表示查詢中執行 select 子句或操作表的順序,id值越大優先級越高,越先被執行。id 相同,執行順序由上至下。 
select_type:查詢類型,SIMPLE、PRIMARY、UNION、DEPENDENT UNION等。 
table:顯示這一行的數據是關於哪張表的 
type:這是重要的列,顯示連接使用了何種類型。從最好到最差的連接類型為const、eq_reg、ref、range、indexhe和all 
possible_keys:顯示可能應用在這張表中的索引。如果為空,沒有可能的索引。可以為相關的域從where語句中選擇一個合適的語句 
key: 實際使用的索引。如果為null,則沒有使用索引。很少的情況下,mysql會選擇優化不足的索引。這種情況下,可以在select語句中使用use index(indexname)來強制使用一個索引或者用ignore index(indexname)來強制mysql忽略索引 
key_len:使用的索引的長度。在不損失精確性的情況下,長度越短越好 
ref:顯示索引的哪一列被使用了,如果可能的話,是一個常數 
rows:mysql認為必須檢查的用來返回請求數據的行數 
extra:關於mysql如何解析查詢的額外信息。

調節MySQL優化器的優化

影響索引的選擇

當我們在執行select * from t8 where id1=1 and id2=0; 語句的時候,優化器會id1_id2_key索引,但我們可以通過IGNORE INDEX、 IGNORE INDEX來影響索引的選擇

強制索引

通過FORCE INDEX(索引1[,索引2])或者使用USE INDEX(索引1[,索引2]),來指定使用哪個索引,也可以指定多個索引,讓優化器從中挑選。 
這里寫圖片描述

這里寫圖片描述

忽略索引

可以使用IGNORE INDEX(索引1[,索引2])來忽略一些索引,這樣優化器,就不會考慮使用這些所有,減少優化器優化時間。 
這里寫圖片描述

影響優化器使用數據表的順序

一般情況下,MySQL優化器會自行決定按照哪種順序掃描數據表才能最快地檢索出數據,但是我們可以通過STRAGHT_JOIN強制優化器按特定的順序使用數據表,畢竟優化器做的判斷不一定都是最優的。使用原則是,讓限制最強的選取操作最先執行。STRAIGHT_JOIN可以放在SELECT后面,也可以放在FROM子句中。 
如下圖 
這里寫圖片描述

這里寫圖片描述 
可以看出,無論from t8,t6還是from t6,t8,都是先檢索t6中的表。但是使用STRAIGHT_JOIN的話,就會按照SQL中順序。 
這里寫圖片描述 
為什么優化器要選擇先判斷t6中的數據呢?一個主要的原因,因為t6中數據更少。 
這里寫圖片描述 
如果將t8中數據刪除幾行后,很明顯MySQL優化器選擇順序數據表的順序就會發生變化。 
這里寫圖片描述

控制SQL語句的優先權

在高並發的網站中,因為MySQL默認的是寫優先,有可能導致一些讀操作有效時間內得不到執行機會,HIGH_PRIORITY可以使用在selectinsert操作中,讓MYSQL知道,這個操作優先進行。 
這里寫圖片描述 
LOW_PRIORITY可以使用在insertupdate操作中,讓mysql知道,這個操作將優先權將降低。 
這里寫圖片描述 
INSERT DELAYED告訴MySQL,這個操作將會延時插入。 
INSERT DELAYED INTO,是客戶端提交數據給MySQL,MySQL返回OK狀態給客戶端。而這是並不是已經將數據插入表,而是存儲在內存里面等待排隊。當mysql有空余時,再插入。另一個重要的好處是,來自許多客戶端的插入被集中在一起,並被編寫入一個塊。這比執行許多獨立的插入要快很多,因為它較少了I/O操作。壞處是,不能返回自動遞增的ID,以及系統崩潰時,MySQL還沒有來得及插入數據的話,這些數據將會丟失。 
這里寫圖片描述

控制查詢緩沖

在實際開發中,一些數據對實時性要求特別高,或者並不經常使用(可能幾天就執行一次或兩次),這樣就需要把緩沖關了,不管這條SQL語句是否被執行過,服務器都不會在緩沖區中查找該數據,每次都會從磁盤中讀取。因為如果實時性要求特別高,緩存中數據可能和磁盤中的就不同步,如果數據不經常使用,被緩存起來,就會占用內存。 
在my.ini中的query_cache_type,使用來控制表緩存的。這個變量有三個取值:0,1,2,分別代表了off、on、demand。 
0:表示query cache 是關閉。 
1:表示查詢總是先到查詢緩存中查找,即使使用了sql_no_cache仍然查詢緩存,因為sql_no_cache只是不緩存查詢結果,而不是不使用查詢結果。 
2:表示只有在使用了SQL_CACHE后,才先從緩沖中查詢數據,仍然將查詢結果緩存起來。 
我本地緩存是關閉的,,如下圖。 
這里寫圖片描述 
關於MySQL緩存可以參考這里 
http://blog.csdn.net/hsd2012/article/details/51526707


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM