MySQL查詢優化器是基於代價(cost-based)的查詢方式。所以,在查詢過程當中,最重要的一部分是根據查詢的SQL語句,依據多種索引,計算查詢須要的代價,從而選擇最優的索引方式生成查詢計劃。 sql
然而,在分析MySQL查詢優化器過程當中,是以查詢優化器主線的原則進行研究,而忽略了不少細節的內容。所以,對MySQL索引選擇進行進一步的研究和分析,給出建立和使用索引的規則,從而有助於分析SQL查詢。 函數
設計 源碼分析
設計原則主要依據爲儘量的測試索引,而不考慮索引的合理性。一方面能夠更加全面的測試在多種索引存在的狀況下,查詢優化器是如何進行選擇的。另外一方面能夠根據選擇索引的原則,評估索引的合理性。 測試
1、數據表設計 優化
數據表設計以下所示,其中students_origin表中只有主鍵索引,students表設計主鍵索引(Primary Key)、惟一索引(Unique Key)、B+索引、聯合索引等。 ui
點擊(此處)摺疊或打開spa
|
2、測試語句設計
具體的查詢SQL語句以下所示:
點擊(此處)摺疊或打開
|
測試
1、主鍵查詢
對主鍵id查詢的測試,主要爲了查看對於主鍵查詢時,在多種索引並存的狀況下,MySQL查詢是如何執行的。以及若是使用了索引,選擇索引的原則。
1.1 源碼分析
首先從源碼角度分析的MySQL查詢優化器的處理邏輯,從而瞭解MySQL是如何進行處理的。
MySQL查詢優化器的核心處理邏輯,在JOIN::optimizer()函數(sql\sql_select.cc:854)中,經過基於代價的查詢處理,選擇代價最低查詢方式執行。對於該查詢來講,因爲沒有任何過濾條件,所以在調用make_join_statistics()函數(sql\sql_select.cc:2651)執行基於代價的查詢處理過程後,查詢執行的類型仍然爲JT_ALL。也就是說,執行該查詢仍然是全表掃描方式。特別說明,MySQL查詢優化器基於代價的查詢處理,主要是根據給出的查詢條件來進行優化的,如where條件、ON條件等。
基於代價的查詢處理以後,會根據查到的最優計劃,生成最終的執行計劃。該過程經過調用make_join_readinfo()函數(sql\sql_select.cc:6818)生成最終的查詢計劃。對於查詢類型爲JT_ALL,若是查詢的表中有索引,則會調用find_shortest_key()函數(sql\sql_select.cc:13438)根據查詢字段的索引覆蓋狀況,以及索引字段的鍵值長度,查找最短的鍵值,進行全表掃描。根據基於代價的查詢處理以後,該查詢的查詢類型爲JT_ALL。若是students中沒有多個索引,只有主鍵索引的狀況下,查詢使用主鍵索引進行查詢。但因爲students表中有多個索引,所以,查找鍵值長度最短的索引idx_rank進行全表掃描。
find_shortest_key()函數的核心代碼以下所示:
點擊(此處)摺疊或打開
|
1.2 執行計劃
首先對僅有主鍵索引的students_origin表,執行SQL查詢,查詢計劃以下所示:
id |
select_type |
table |
type |
possible_keys |
key |
key_len |
ref |
row |
Extra |
1 |
SIMPLE |
students_origin |
index |
NULL |
PRIMARY |
4 |
NULL |
10 |
Using index |
對studentns數據表進行相同的SQL查詢,查詢的執行計劃以下所示:
id |
select_type |
table |
type |
possible_keys |
key |
key_len |
ref |
row |
Extra |
1 |
SIMPLE |
students |
index |
NULL |
idx_rank |
5 |
NULL |
10 |
Using index |
從執行計劃來看,第一個查詢的查詢類型爲index,鍵值爲主鍵索引,而第二個查詢使用idx_rank索引進行全表掃描。
對於相同的SQL查詢語句,第二個查詢使用的索引起生了變化。緣由與Innodb數據和索引的存儲有關,具體Innodb數據和索引的存儲內容在MySQL官方文檔和《High Performance MySQL》中都有相關的講解。而且對於Innodb數據索引存儲的源碼實現邏輯,將進一步深刻分析和測試,這裏僅簡要解釋相關的內容。首先Innodb是彙集存儲的,每條記錄的數據以主鍵進行彙集存儲,經過主鍵進行索引。若是沒有定義主鍵,系統默認使用6個字符做爲主鍵進行彙集存儲。而輔助索引(secondary indexes)中的每條記錄包含主鍵(primary key)和索引字段。
所以,對於該查詢,查詢優化器使用輔助索引進行查詢,經過輔助索引就能夠找到查詢的id,查找的代價更低。
2、惟一索引查詢
對惟一索引字段進行查詢的測試,主要用於查看輔助索引在查詢索引字段的狀況下,MySQL選擇索引的原則。
2.1 源碼分析
從源碼分析MySQL查詢優化器,與1.1的邏輯處理流程基本一致。因爲沒有任何過濾條件,所以查詢優化器調用make_join_statistics()函數進行基於代價的查詢過程後,查詢類型爲JT_ALL。在生成查詢計劃時,調用find_shortest_key()函數查找最短的鍵值,進行查詢全表。因爲惟一索引name可以被索引覆蓋,所以經過惟一索引來查詢全表。
2.2查詢計劃
對students_origin表執行SQL查詢,查詢計劃以下所示:
id |
select_type |
table |
type |
possible_keys |
key |
key_len |
ref |
row |
Extra |
1 |
SIMPLE |
students_origin |
ALL |
NULL |
NULL |
NULL |
NULL |
10 |
對students表執行SQL查詢,查詢計劃以下所示:
id |
select_type |
table |
type |
possible_keys |
key |
key_len |
ref |
row |
Extra |
1 |
SIMPLE |
students |
index |
NULL |
name |
93 |
NULL |
10 |
Using index |
由查詢計劃可知,對name字段沒有惟一索引的表進行查詢時,查詢進行全表掃描,查詢類型爲ALL。而對name字段使用惟一索引的students表查詢時,使用惟一索引進行全表掃描。查詢類型爲index,而非ALL。
經過以上分析可知,當查詢的SQL能夠經過輔助索引獲得查詢結果時,MySQL查詢優化器會選擇輔助索引進行查詢優化,這有效的下降了查詢的代價。
3、多個索引查詢
對索引字段建立索引和聯合索引,查看MySQL查詢優化器是如何選擇索引,來提升查詢效率的。
3.1 源碼分析
從源碼分析來看,MySQL查詢優化器的邏輯處理流程與1.1基本一致。對於全表查詢,make_join_statistics()函數進行基於代價的查詢處理後,查詢類型爲JT_ALL。因爲rank字段有兩個索引,在調用find_shortest_key()函數,查找idx_rank(長度爲5)和idx_rank_total(長度爲10)中最短的鍵值,最終選擇idx_rank進行查詢。因爲查詢字段id, rank可以被索引覆蓋,所以經過idx_rank索引來查詢。
3.2查詢計劃
對students表執行SQL查詢,查詢計劃以下所示:
id |
select_type |
table |
type |
possible_keys |
key |
key_len |
ref |
row |
Extra |
1 |
SIMPLE |
students |
index |
NULL |
idx_rank |
5 |
NULL |
10 |
Using index |
從查詢計劃來看,在rank被idx_rank和idx_rank_total兩個索引都覆蓋的狀況下,MySQL查詢優化器選擇鍵值長度短的idx_rank進行查詢。
4、聯合索引查詢
使用聯合索引,主要用於對比在查詢中使用過濾條件時,選擇索引的不一樣,從而分析MySQL查詢優化器的處理邏輯。
4.1 源碼分析
從源碼分析來看,MySQL查詢優化器的邏輯處理流程與3.1基本一致。不一樣之處在於調用find_shortest_key()函數時,僅有idx_rank_total索引覆蓋查詢字段。所以查詢優化器選擇idx_rank_total索引進行覆蓋索引查詢。
4.2查詢計劃
對students表執行SQL查詢,查詢計劃以下所示:
id |
select_type |
table |
type |
possible_keys |
key |
key_len |
ref |
row |
Extra |
1 |
SIMPLE |
students |
index |
NULL |
idx_rank_total |
10 |
NULL |
10 |
Using index |
從查詢計劃來看,MySQL查詢優化器使用覆蓋索引idx_rank_total進行查詢,該查詢測試主要用於對比測試5中的結果。
5、條件索引查詢
經過where條件,對查詢內容進行過濾。該測試主要用於查看,rank在where過濾條件中時,與沒有過濾條件的測試4進行比較,查看查詢優化器選擇索引的原則。
5.1 源碼分析
經過源碼可知,對於有where過濾條件的SQL查詢,查詢優化器在make_join_statistics()函數中,根據基於代價的原則,經過調用update_ref_and_keys()函數(sql\sql_select.cc:3963),查找rank可使用的索引有idx_rank和idx_rank_total兩個索引,而後調用check_quick_keys()函數(sql\opt_range.cc:7628)查找對查詢條件索引的記錄數,MySQL查詢優化器選擇查詢代價最低的索引idx_rank。查詢優化器經過基於代價的處理後,查詢類型爲JT_REF。所以,生成查詢計劃時,因爲已經選定了使用的索引,因此沒必要再調用find_shortest_key()函數再去查找鍵值最短的索引。
5.2 查詢計劃
對students表執行SQL查詢,查詢計劃以下所示:
id |
select_type |
table |
type |
possible_keys |
key |
key_len |
ref |
row |
Extra |
1 |
SIMPLE |
students |
ref |
idx_rank,idx_rank_total |
idx_rank |
5 |
const |
1 |
Using where |
從查詢計劃來看,查詢類型爲ref,查詢使用的索引爲idx_rank,而且查詢使用的是where條件,而沒有使用索引覆蓋進行查詢。也就是說,首先經過索引idx_rank查找到鍵值,經過主鍵id查找對應的記錄。
與測試4.2相比,查詢計劃發生了改變。主要緣由是因爲where條件改變了查詢優化器的處理邏輯。查詢優化器根據查詢條件經過基於代價的處理,選擇查詢代價最低的索引,從而生成查詢計劃。而與4.2相比,查詢優化器選擇經過覆蓋索引來進行查詢,而不進行全表掃描。
在實際應用中,某些狀況下,經過where條件進行基於代價的處理,選擇where條件字段的索引,反而不如經過覆蓋索引進行過濾where條件的查詢代價低。正如測試5的查詢,若是查詢的字段能夠經過覆蓋索引進行查詢,並經過where條件過濾的方式,可能比查詢優化器經過where條件進行查找最優的索引查找,再經過對應主鍵進行查詢更高效。該問題已超出範圍,將在以後進行詳細測試和分析。
結論
經過以上測試,對MySQL查詢優化器選擇索引的原則有較深刻的理解,經過對SQL查詢進行分析,有助於判斷查詢類型和選擇的索引。
MySQL查詢優化器的在索引選擇的規則能夠歸納爲:
1、對無過濾條件、索引能夠覆蓋的查詢。查詢優化器選擇覆蓋索引鍵值最短的索引進行查詢;
2、對無過濾條件、無索引覆蓋的查詢。查詢優化器選擇全表掃描;
3、對有過濾條件、索引能夠覆蓋的查詢。查詢優化器優先基於代價的方式對過濾條件進行處理。若是能夠索引查找,將選擇代價最低的索引進行查找。若是是全表掃描,則經過查找鍵值最短的覆蓋索引進行查詢,並經過過濾條件進行過濾。
4、對有過濾條件、無索引覆蓋的查詢。查詢優化器基於代價的方式對過濾條件進行處理,生成查詢計劃。