MySQL 索引選擇原則

目的

         MySQL查詢優化器是基於代價(cost-based)的查詢方式。所以,在查詢過程當中,最重要的一部分是根據查詢的SQL語句,依據多種索引,計算查詢須要的代價,從而選擇最優的索引方式生成查詢計劃。 sql

         然而,在分析MySQL查詢優化器過程當中,是以查詢優化器主線的原則進行研究,而忽略了不少細節的內容。所以,對MySQL索引選擇進行進一步的研究和分析,給出建立和使用索引的規則,從而有助於分析SQL查詢。 函數

設計 源碼分析

         設計原則主要依據爲儘量的測試索引,而不考慮索引的合理性。一方面能夠更加全面的測試在多種索引存在的狀況下,查詢優化器是如何進行選擇的。另外一方面能夠根據選擇索引的原則,評估索引的合理性。 測試

1、數據表設計 優化

         數據表設計以下所示,其中students_origin表中只有主鍵索引,students表設計主鍵索引(Primary Key)、惟一索引(Unique Key)、B+索引、聯合索引等。 ui

 


 

點擊(此處)摺疊或打開spa

  1. CREATE TABLE `students_origin` (
    設計


  2.   `id` int(11) NOT NULL,
    orm


  3.   `name` varchar(30) DEFAULT NULL,
    索引


  4.   `age` int(11) DEFAULT NULL,


  5.   `major` varchar(20) DEFAULT NULL,


  6.   `rank` int(11) DEFAULT NULL,


  7.   `total` int(11) DEFAULT NULL,


  8.   `comment` varchar(20) DEFAULT NULL,


  9.   PRIMARY KEY (`id`)


  10. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


  11.  


  12. CREATE TABLE `students` (


  13.   `id` int(11) NOT NULL,


  14.   `name` varchar(30) DEFAULT NULL,


  15.   `age` int(11) DEFAULT NULL,


  16.   `major` varchar(20) DEFAULT NULL,


  17.   `rank` int(11) DEFAULT NULL,


  18.   `total` int(11) DEFAULT NULL,


  19.   `comment` varchar(20) DEFAULT NULL,


  20.   PRIMARY KEY (`id`),


  21.   UNIQUE KEY `name` (`name`),


  22.   KEY `idx_major` (`major`),


  23.   KEY `idx_rank` (`rank`),


  24.   KEY `idx_rank_total` (`rank`,`total`)


  25. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


 

 

2、測試語句設計

         具體的查詢SQL語句以下所示:


點擊(此處)摺疊或打開

  1. 一、select id from students;(students_origin)


  2. 二、select id, name from students; (students_origin)


  3. 三、select id, rank from students;


  4. 四、select id, rank, total from students;


  5. 五、select id, rank, total from students where rank = 2;


 測試

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. for (uint nr=0; nr < table->s->keys ; nr++)


  2.     {


  3.       if (nr == usable_clustered_pk)


  4.         continue;


  5.       if (usable_keys->is_set(nr))


  6.       {


  7.         if (table->key_info[nr].key_length < min_length)


  8.         {


  9.           min_length=table->key_info[nr].key_length;


  10.           best=nr;


  11.         }


  12.       }


  13.     }

 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

 

         從查詢計劃來看,在rankidx_rankidx_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條件,對查詢內容進行過濾。該測試主要用於查看,rankwhere過濾條件中時,與沒有過濾條件的測試4進行比較,查看查詢優化器選擇索引的原則。

5.1 源碼分析

         經過源碼可知,對於有where過濾條件的SQL查詢,查詢優化器在make_join_statistics()函數中,根據基於代價的原則,經過調用update_ref_and_keys()函數(sql\sql_select.cc:3963),查找rank可使用的索引有idx_rankidx_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、對有過濾條件、無索引覆蓋的查詢。查詢優化器基於代價的方式對過濾條件進行處理,生成查詢計劃。

相關文章
相關標籤/搜索