一、Mysql索引基于B+樹
B+樹基于平衡二叉查找樹和B+樹。所謂平衡二叉查找樹,就是任意節點的2個子樹的最大高度差是1。平衡二叉樹比非平衡二叉樹的查找效率要高的多,平均時間復雜度是O(log2n)。為了保持二叉樹的平衡性,插入和刪除節點時往往要進行左旋和右旋操作。
B+樹是為磁盤和其他直接存取輔助設備設計的一種平衡查找樹。在B+樹中,所有記錄節點都是按鍵值大小順序存放在同一層的葉子節點上,由各葉子節點指針進行連接。B+樹的查找時間復雜度是O(logmn),其中m是節點的子樹個數。
?B+樹的特點:1. 是一棵n叉樹;2. 節點有序;3. 非葉子節點不存儲數據只存儲索引,同樣大小的磁盤頁可以容納更多的節點元素。所以數據量相同的情況下,B+樹比B樹更加“矮胖“,因此使用的IO查詢次數更少。
B+樹的插入操作是通過頁的拆分和旋轉來維持B+樹的平衡,刪除操作由于受填充因子的影響需要頁合并來維持平衡。
在數據庫中B+樹的高度一般在2-4層,也就是說查找某一鍵值的行記錄時最多需要2到4次IO。
二、索引分為聚集索引和輔助索引
聚集索引:葉子節點存放整行數據,通常主鍵索引即為聚集索引。在Innodb存儲引擎中,存儲引擎表就是索引組織表,表中數據就是按照主鍵順序存放的。頁內的數據以及頁之間都是以雙向鏈表連接。通過聚集索引樹進行搜索,可以直接查找到整行數據。
輔助索引:葉子節點存儲的是鍵值以及主鍵。在Innodb存儲引擎中,通過輔助索引樹進行搜索找到的是聚集索引鍵,因此如果要找到整行數據還需要通過聚集索引樹搜索,這個過程稱為回表。
三、查看索引
查看索引的命令:show index from table。一個只有主鍵索引的表執行改命令得到的結果可能如下:
結果中字段解釋如下:
1. Table:表名稱;
2. Non_unique:該索引是否包含重復值。主鍵索引的列值是唯一的。
3. Key_name:索引名稱。PRIMARY代表主鍵索引。
4. Seq_in_index: 索引中序列的序列號,從1開始,如果是組合索引 那么按照字段在建立索引時的順序排列 如 ('c1', 'c2', 'c3') 那么 分別為 1, 2, 3。
5. Column_name:列的名稱。
6. Collation: 列以什么方式存儲在索引中。在MySQL中,有值‘A’(升序)或NULL(無分序)。
7. Cardinality:索引中唯一值的數目的估計值,優化器會根據這個值來判斷是否使用這個索引。對于大表而言計算這個值可能需要很長時間,因此這個值并不是實時更新的。通過運行ANALYZE TABLE 或者 myisamchk -a 來更新。
8. Sub_part:索引的長度,如果是部分被編入索引 則該值表示索引的長度 ,如果是整列被編入索引則為null。
9. Packed:指示關鍵字如何被壓縮。如果沒有被壓縮,則為NULL。
10. Null:如果該列的值有NULL,則是YES,否則為NO。
11. Index_type:索引類型(BTREE, FULLTEXT, HASH, RTREE)
12. Commnet:關于在其列中沒有描述的索引的信息。
13. Index_comment:為索引創建時提供了一個注釋屬性的索引的任何評論。
四、管理索引
1. 創建索引
創建索引可以使用 create index 或者 alter table xxx add index 語句來實現。
用戶可以設置對整個列進行索引,也可以只索引一個列的開頭部分數據。
一般來說,在WHERE和JOIN中出現的列需要建立索引,但也不完全如此,因為MySQL只對<,<=,=,>,>=,BETWEEN,IN,以及某些時候的LIKE才會使用索引。
2. 刪除索引
刪除索引可以使用 drop index 或者 alter table xxx drop index 語句來實現。
五、cardinality
一般而言,高選擇性的列創建索引比較合適。所謂高選擇性,即不同值的各數和記錄總數越接近越好。可以通過 show index 的 cardinality 來觀察。
1. cardinality 何時更新:表中 1/16的數據發生過變化 或者 表中數據發生活 20億次變化;執行語句 analyze table、show table status、show index 時會觸發更新。
2. cardinality 如何計算:該值是基于采樣統計出來的(在葉子節點中隨機取8個節點,按照 n *N /8 計算而來。n為這8個節點中不同值的個數),且每次結果可能不同。
六、聯合索引
假定2個鍵值的名稱分別是a、b,創建索引(a,b),其B+樹如下圖所示。
????? 聯合索引的鍵值也是按照邏輯順序排序的。
????? 對于查詢 SELECT * FROM TABLE WHERE a=xxx and b=xxx,可以使用到(a,b)這個聯合索引,對于單個a列的查詢 SELECT * FROM TABLE WHERE a=xxx也可以使用(a,b)這個聯合索引,但是對于b列的查詢則無法使用到這個聯合索引。因為b列的值分別為1、2、1、4、1、2,顯然不是排過序的。
????? 當有單個索引和聯合索引都可以使用時,優化器可能會選擇單個索引,因為單個索引理論上一頁存放的節點更多,IO次數更少。
????? 如果聯合索引能夠覆蓋排序字段的話,優化器可能會選擇聯合索引,因為這樣無須再做額外的排序。仍然以上圖為例,假設有列a上的索引index_a以及列(a,b)上的聯合索引index_a_b,對于查詢語句 SELECT * FROM TABLE WHERE a=xxx order by b,優化器將會選擇index_a_b。
七、索引覆蓋
????? 所謂索引覆蓋指的是不需要通過回表,只需要查詢非聚集索引就能夠查到需要的所有字段。在SQL執行計劃里,如果Extra信息包括 Using Index 則表明使用了覆蓋索引。
????? 優化器會根據where條件和select_list里面的字段決定在使用一個索引(sta)后,是否需要回表—回到聚集索引取數據。基本的做法是:在確定了一個索引后,將select_list和where中出現的所有字段都拿來判斷一下,如果字段都存在于sta索引中,則可以使用覆蓋索引。如現有索引(a,b),語句 select count (*) from table where b > 10 and b < 20 是可以用到覆蓋索引的,因為聯合索引 (a, b) 覆蓋了查詢需要的字段b,換句話說,通過索引(a,b)的索引樹查找到葉子節點并按照條件進行過濾即可得到結果,無需回表。
????? 當有多個索引能夠覆蓋掃描時,優化器會選擇效率最高的 ( IO次數最少的)。例如對于 SELECT COUNT(*) FROM TABLE 這樣的查詢,如有主鍵索引和非聚簇索引,優化器會選擇非聚簇索引,因為非聚簇索引節點數據更小,io次數更少。
八、不使用索引的情況
??????? 對于不能進行索引覆蓋的情況,優化器選擇輔助索引的情況是:通過輔助索引查找到的數據是少量的。這是由當前硬盤的特性所決定的,即利用順序讀來替換隨機讀的查找。若用戶使用的磁盤是固態硬盤,隨機讀操作非常快,同時有足夠的自信來確認使用輔助索引可以帶來更好的性能,那么可以使用關鍵字 FORCE INDEX 來強制使用某個索引。
??????? 假設表employees上有索引 index_hire_date(hire_date),語句 select * from employees where hire_date > '1990-01-01' 并不會使用輔助索引而是全表掃描,正是因為查詢條件查找到的數據量可能很大,對這些數據隨機讀取數據行會很耗時。而 語句 select * from employees where hire_date = '1990-01-01' 則會使用輔助索引。
九、MRR優化
??????? Multi-Range Read優化的目的是減少磁盤隨機訪問,將隨機訪問轉換為順序訪問。其工作方式為:1. 將通過輔助索引查到的輔助索引鍵和主鍵對存儲在緩存中;2. 緩存滿了后根據主鍵進行排序;3. 根據排序后的主鍵來訪問表中數據。
??????? 給出一個簡單的例子,在innodb表執行下面的查詢:
SELECT non_key_column FROM tbl WHERE key_column=x
??????? 在沒有MRR的情況下,它是這樣得到結果的:
1. select key_column, pk_column from tb where key_column=x order by key_column ---> 假設這個結果集是t
2. for each row in t ; select non_key_column from tb where pk_column = pk_column_value。(在oracle里第2步叫回表)
??????? 在有MRR的情況下,它是這樣執行的:
1. select key_column, pk_column from tb where key_column = x order by key_column ---> 假設這個結果集是t
2. 將結果集t放在buffer里面(直到buffer滿了),然后對結果集t按照pk_column排序 ---> 假設排序好的結果集是t_sort
3. select non_key_column fromtb where pk_column in (select pk_column from t_sort)
??????? 兩者的區別主要是兩點:
1. 沒有MRR的情況下,隨機IO增加,因為從二級索引里面得到的索引元組是有序,但是他們在主鍵索引里面卻是無序的,所以每次去主鍵索引里面得到non_key_column的時候都是隨機IO。(如果索引覆蓋,那也就沒必要利用MRR的特性了,直接從索引里面得到所有數據)
2. 沒有MRR的情況下,訪問主鍵索引的次數增加。沒有MRR的情況下,二級索引里面得到多少行,那么就要去訪問多少次主鍵索引(也不能完全這樣說,因為mysql實現了BNL),而有了MRR的時候,次數就大約減少為之前次數t/buffer_size。
??????? 此外,MRR還會將某些范圍查詢拆分成鍵值對,以此來進行批量的數據查詢。例如:SELECT * FROM t WHERE key_column1 > 1000 AND key_column1 < 2000 and key_column2 = 10000,表中有(key_column1, key_column2)的聯合索引。如果沒有MRR,優化器會在聯合索引樹中取出 key_column1 在1000到2000之間的數據,再過濾出來 key_column2 =10000的數據。這會導致大量無用數據被取出。啟用MRR后,優化器將查詢條件拆分為 (1000, 100000), (1001, 10000)……(1999, 10000),直接查詢出數據。
十、Index Condition Pushdown 優化
??????? 在開啟ICP之前,優化器根據索引取得主鍵后,再根據主鍵取得相應記錄后再進行篩選。
??????? 開啟ICP后,優化器在取出索引的同時會判斷是否可以進行where條件的過濾,如果可以過濾則直接進行過濾,然后再取得相應的記錄。
??????? 假設某張表有聯合索引(zip_code, last_name, first_name),對于查詢語句 SELECT * FROM TABLE WHERE zip_code = '90988' And last_name LIKE '%etrunia%' AND address LIKE '%Main Street%'。由于2個like條件是非固定前綴匹配,因此只能通過該索引匹配出zip_code=90988的索引節點。如果不開啟ICP,則數據庫需要先通過索引取出所有zip_code = 90988 的記錄,然后再過濾where條件。開后ICP后,取出 zip_code = 90988的同時會在索引中過濾出來 last_name 和 address 符合條件的索引,再取出相關記錄。
??????? 使用了ICP的執行計劃Extra列會顯示 Using Index Condition。
十一、哈希索引
??????? Innodb引擎本身不支持手動創建哈希索引,Innodb存儲引擎會監控對表上二級索引的查找,如果發現某二級索引被頻繁訪問,二級索引成為熱數據,會自動建立哈希索引來提升速度。
??????? 哈希表也為散列表,又直接尋址改進而來。在哈希的方式下,一個元素k處于h(k)中,即利用哈希函數h,根據關鍵字k計算出槽的位置。函數h將關鍵字域映射到哈希表T[0...m-1]的槽位上。有可能將兩個不同的關鍵字映射到相同的位置,這叫做碰撞,在數據庫中一般采用鏈接法來解決。在鏈接法中,將散列到同一槽位的元素放在一個鏈表中。
??????? Hash索引結構的特殊性,其檢索效率非常高,索引的檢索可以一次定位,不像B-Tree索引需要從根節點到枝節點,最后才能訪問到頁節點這樣多次的IO訪問,所以Hash索引的查詢效率要遠高于B-Tree索引。
??????? 哈希索引的限制:
??????? 1. 哈希索引只包含哈希值和行指針,而不存儲字段值,所以不能使用索引中的值來避免讀取行(即不能使用哈希索引來做覆蓋索引掃描),不過,訪問內存中的行的速度很快(因為memory引擎的數據都保存在內存里),所以大部分情況下這一點對性能的影響并不明顯。
??????? 2. 哈希索引數據并不是按照索引列的值順序存儲的,所以也就無法用于排序
??????? 3. 哈希索引也不支持部分索引列匹配查找,因為哈希索引始終是使用索引的全部列值內容來計算哈希值的。如:數據列(a,b)上建立哈希索引,如果只查詢數據列a,則無法使用該索引。
??????? 4. 哈希索引只支持等值比較查詢,如:=,in(),<=>(注意,<>和<=>是不同的操作),不支持任何范圍查詢(必須給定具體的where條件值來計算hash值,所以不支持范圍查詢)。
??????? 5. 訪問哈希索引的數據非常快,除非有很多哈希沖突,當出現哈希沖突的時候,存儲引擎必須遍歷鏈表中所有的行指針,逐行進行比較,直到找到所有符合條件的行。
??????? 6. 如果哈希沖突很多的話,一些索引維護操作的代價也很高,如:如果在某個選擇性很低的列上建立哈希索引(即很多重復值的列),那么當從表中刪除一行時,存儲引擎需要遍歷對應哈希值的鏈表中的每一行,找到并刪除對應的引用,沖突越多,代價越大。
十二、 Mysql 執行計劃
各字段詳解
id
select查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序?。
select_type
查詢的類型,主要是用于區分普通查詢、聯合查詢、子查詢等復雜的查詢
1、SIMPLE:簡單的select查詢,查詢中不包含子查詢或者union
2、PRIMARY:查詢中包含任何復雜的子部分,最外層查詢則被標記為primary
3、SUBQUERY:在select 或 where列表中包含了子查詢
4、DERIVED:在from列表中包含的子查詢被標記為derived(衍生),mysql或遞歸執行這些子查詢,把結果放在零時表里
5、UNION:若第二個select出現在union之后,則被標記為union;若union包含在from子句的子查詢中,外層select將被標記為derived
6、UNION RESULT:從union表獲取結果的select
type
訪問類型,sql查詢優化中一個很重要的指標,結果值從好到壞依次是:
system>const>eq_ref>ref> fulltext > ref_or_null > index_merge > unique_subquery > index_subquery >range>index>ALL
一般來說,好的sql查詢至少達到range級別,最好能達到ref
1、system:表只有一行記錄(等于系統表),這是const類型的特例,平時不會出現,可以忽略不計
2、const:表示通過索引一次就找到了,const用于比較primary key 或者 unique索引。因為只需匹配一行數據,所有很快。如果將主鍵置于where列表中,mysql就能將該查詢轉換為一個const
3、eq_ref:唯一性索引掃描,對于每個索引鍵,表中只有一條記錄與之匹配。常見于主鍵 或 唯一索引掃描。
4、ref:非唯一性索引掃描,返回匹配某個單獨值的所有行。本質是也是一種索引訪問,它返回所有匹配某個單獨值的行,然而他可能會找到多個符合條件的行,所以它應該屬于查找和掃描的混合體。
5、range:只檢索給定范圍的行,使用一個索引來選擇行。key列顯示使用了那個索引。一般就是在where語句中出現了bettween、<、>、in等的查詢。這種索引列上的范圍掃描比全索引掃描要好。只需要開始于某個點,結束于另一個點,不用掃描全部索引。
6、index:Full Index Scan,index與ALL區別為index類型只遍歷索引樹。這通常為ALL塊,應為索引文件通常比數據文件小。(Index與ALL雖然都是讀全表,但index是從索引中讀取,而ALL是從硬盤讀取)。
7、ALL:Full Table Scan,遍歷全表以找到匹配的行。
possible_keys
查詢涉及到的字段上存在索引,則該索引將被列出,但不一定被查詢實際使用。
key
實際使用的索引,如果為NULL,則沒有使用索引。
查詢中如果使用了覆蓋索引,則該索引僅出現在key列表中。
key_len
表示索引中使用的字節數,查詢中使用的索引的長度(最大可能長度),并非實際使用長度,理論上長度越短越好。key_len是根據表定義計算而得的,不是通過表內檢索出的。
ref
顯示索引的那一列被使用了,如果可能,是一個常量const。
rows
根據表統計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數。
Extra
不適合在其他字段中顯示,但是十分重要的額外信息
1、Using filesort:
mysql對數據使用一個外部的索引排序,而不是按照表內的索引進行排序讀取。也就是說mysql無法利用索引完成的排序操作成為“文件排序”。
2、Using temporary:
使用臨時表保存中間結果,也就是說mysql在對查詢結果排序時使用了臨時表,常見于order by 和 group by。
3、Using index:
表示相應的select操作中使用了覆蓋索引(Covering Index),避免了訪問表的數據行,效率高
如果同時出現Using where,表明索引被用來執行索引鍵值的查找(參考上圖)
如果沒用同時出現Using where,表明索引用來讀取數據而非執行查找動作。
覆蓋索引(Covering Index):也叫索引覆蓋。就是select列表中的字段,只用從索引中就能獲取,不必根據索引再次讀取數據文件,換句話說查詢列要被所建的索引覆蓋。
注意:
a、如需使用覆蓋索引,select列表中的字段只取出需要的列,不要使用select *
b、如果將所有字段都建索引會導致索引文件過大,反而降低crud性能
4、Using where :
使用了where過濾
5、Using join buffer :
使用了鏈接緩存
6、Impossible WHERE:
where子句的值總是false,不能用來獲取任何元祖。
7、select tables optimized away:
在沒有group by子句的情況下,基于索引優化MIN/MAX操作或者對于MyISAM存儲引擎優化COUNT(*)操作,不必等到執行階段在進行計算,查詢執行計劃生成的階段即可完成優化
8、distinct:
優化distinct操作,在找到第一個匹配的元祖后即停止找同樣值得動作