Mysql學習-索引總結(B-tree和hash、主鍵索引、唯一索引、普通索引、全文索引和組合索引)

常用的兩種索引結構:B-tree和HASH

B-tree

B-tree索引能夠加快訪問數據的速度,因爲存儲引擎不再需要經行全表掃描來獲取需要的數據,取而代之的是從根節點開始搜索。根節點的槽中存放了指向子節點的指針,存儲引擎根據這些指針向下查找。通常比較節點頁的值和要查找的值可以找到合適的指針進入下層子節點。

B-tree通常意味着所有的值都是按順序存儲的,並且每一個葉子頁到根的距離相同。

如上圖,是一顆B-tree,關於B-tree的定義可以參見B-tree,這裏只說一些重點,淺藍色的塊我們稱之爲一個磁盤塊,可以看到每個磁盤塊包含幾個數據項(深藍色所示)和指針(黃色所示),如磁盤塊1包含數據項17和35,包含指針P1、P2、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。真實的數據存在於葉子節點即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非葉子節點只不存儲真實的數據,只存儲指引搜索方向的數據項,如17、35並不真實存在於數據表中。

B-tree的查找過程
如圖所示,如果要查找數據項29,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間因爲非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內存,發生第三次IO,同時內存中做二分查找找到29,結束查詢,總計三次IO。真實的情況是,3層的B-tree可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那麼總共需要百萬次的IO,顯然成本非常非常高。

B-tree性質
1.通過上面的分析,我們知道IO次數取決於b+數的高度h,假設當前數據表的數據爲N,每個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N一定的情況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,如果數據項佔的空間越小,數據項的數量越多,樹的高度越低。這就是爲什麼每個數據項,即索引字段要儘量的小,比如int佔4字節,要比bigint8字節少一半。這也是爲什麼B-tree要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度下降,導致樹增高。當數據項等於1時將會退化成線性表。
2.當B-tree的數據項是複合的數據結構,比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜索樹的,比如當(張三,20,F)這樣的數據來檢索的時候,B-tree會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最後得到檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,B-tree就不知道下一步該查哪個節點,因爲建立搜索樹的時候name就是第一個比較因子,必須要先根據name來搜索才能知道下一步去哪裏查詢。比如當(張三,F)這樣的數據來檢索時,B-tree可以用name來指定搜索方向,但下一個字段age的缺失,所以只能把名字等於張三的數據都找到,然後再匹配性別是F的數據了, 這個是非常重要的性質,即索引的最左匹配特性。

注意:B-tree的高度一般都在2-4層,這也就是說查找某一鍵值的行記錄最多隻要2到4次IO,花費0.02-0.04秒左右。

B-tree索引適用於全值匹配、匹配最左前綴、匹配列前綴、匹配範圍值

建立索引的原則

1.     最左前綴匹配原則,非常重要的原則,mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整。
2.=和in可以亂序,比如a = 1 and b = 2and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式
3.儘量選擇區分度高的列作爲索引,區分度的公式是count(distinctcol)/count(*),表示字段不重複的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄
4.索引列不能參與計算,保持列“乾淨”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都應用函數才能比較,顯然成本太大。所以語句應該寫成create_time = unix_timestamp(’2014-05-29’);
5.儘量的擴展索引,不要新建索引。比如表中已經有a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可。

舉例子:
數據表如下,

 

[sql] view plain copy

  1. mysql>desc newCity;  
  2. +-------------+----------+------+-----+---------+----------------+  
  3. |Field       | Type     | Null | Key | Default | Extra          |  
  4. +-------------+----------+------+-----+---------+----------------+  
  5. | Id          | int(11)  | NO  | PRI | NULL    | auto_increment |  
  6. |Name        | char(35) | NO   |    |         |                |  
  7. |CountryCode | char(20) | NO   | MUL|         |                |  
  8. |District    | char(20) | NO   |    |         |                |  
  9. |Population  | int(11)  | NO  |     | 0       |                |  
  10. +-------------+----------+------+-----+---------+----------------+  
  11. 5 rows in set (0.00 sec)  

 

 

數據量:

 

[sql] view plain copy

  1. mysql>select count(*) from newCity;  
  2. +----------+  
  3. | count(*)|  
  4. +----------+  
  5. |     4079 |  
  6. +----------+  
  7. 1 row in set (0.01 sec)  

 

我們通過explain查看執行計劃

首先我們在沒有添加索引時,進行如下查詢。我們可以看出type=all表明全表掃描,估計查詢行數爲4070行。

 

[sql] view plain copy

  1. mysql>explain select * from newCity where CountryCode="BRA" and District="Bahia"and Name="Itabuna"\G  
  2. ***************************1. row ***************************  
  3.            id: 1  
  4.   select_type: SIMPLE  
  5.         table: newCity  
  6.    partitions: NULL  
  7.          type: ALL  
  8. possible_keys:NULL  
  9.           key: NULL  
  10.       key_len: NULL  
  11.           ref: NULL  
  12.          rows: 4070  
  13.      filtered: 0.10  
  14.         Extra: Using where  
  15. 1 row in set, 1 warning (0.00 sec)  

 

現在我們添加索引如下,

 

[sql] view plain copy

  1. mysql>alter table newCity  
  2.     -> add index idx (CountryCode,District,Name);  
  3. Query OK,0 rows affected (0.17 sec)  
  4. Records: 0  Duplicates: 0  Warnings: 0  

 

在通過explain查詢執行計劃,我們發現該查詢的行數估計爲1行。

 

[sql] view plain copy

  1. mysql>explain select * from newCity where CountryCode="BRA" andDistrict="Bahia" and Name="Itabuna"  
  2. \G  
  3. ***************************1. row ***************************  
  4.            id: 1  
  5.   select_type: SIMPLE  
  6.         table: newCity  
  7.    partitions: NULL  
  8.          type: ref  
  9. possible_keys:idx  
  10.           key: idx  
  11.       key_len: 225  
  12.           ref: const,const,const  
  13.          rows: 1  
  14.      filtered: 100.00  
  15.         Extra: NULL  
  16. 1 row in set, 1 warning (0.00 sec)  

 

哈希索引

Mysql中只有在memory引擎顯示支持哈希索引。

哈希索引基於哈希表實現,只有精確匹配索引所有列的列纔有效。對於每一行數據,存儲引擎都會對所有索引計算一個哈希碼,哈希碼是一個較小的值並且不同鍵值計算出來的哈希碼都不一樣。哈希索引將所有的哈希碼存儲在索引中,同時在哈希表中保存指向每個數據的指針。

哈希索引的限制:

l  哈希索引只包含哈希值和行指針,而不知存儲字段值,所以不能使用索引中的值來避免讀取行。若讀取行,則必須經行一次IO操作。

l  哈希索引並不是按照哈希值順序存儲的,所以也就無法用於排序。

l  哈希值也不支持部分索引,因爲哈希值始終是使用索引列中所有的內容計算哈希值的。例如,在數據(A,B)上建立哈希索引,如果只查詢A則不能使用哈希索引。

l  哈希索引只支持等值查詢。

l  哈希索引查詢速度非常快,除非出現出現衝突。

注意:Innodb引擎有一個特殊的功能叫做“自適應哈希索引”。當Innodb注意到某些索引值使用非常頻繁時,它會在內存中基於B-tree索引之上再建立一個哈希索引。這是一個完全自動、內部的行爲,用戶無法配置或者設置,不過有必要可以關閉此功能。

創建自定義哈希索引

思路:在B-tree基礎上創建一個僞哈希索引。這和真正的哈希索引不是一回事,它使用哈希值而不是鍵本身經行查找。需要在操作中在where語句中手動指定哈希函數。

 

[sql] view plain copy

  1. mysql> createtable test(  
  2.     -> id int not null auto_increment,  
  3.     -> url varchar(255) not null,  
  4.     -> url_crc int unsigned not null default0,  
  5.     -> primary key (id)  
  6. -> );  

 

其中,url_crc用來存儲哈希值。該值根據url和哈希函數得出。

我們通過觸發器來實現維護哈希值。

 

[sql] view plain copy

  1. mysql>delimiter //  
  2. mysql> createtrigger test_insert before insert on test for each row begin  
  3.     -> set new.url_crc=crc32(new.url);  
  4.     -> end;  
  5.     -> //  
  6. Query OK, 0 rowsaffected (0.03 sec)  
  7. mysql> createtrigger test_update before update on test for each row begin  
  8.     -> set new.url_crc=crc32(new.url);  
  9.     -> end;  
  10.     -> //  
  11. Query OK, 0 rowsaffected (0.03 sec)  
  12. mysql>delimiter ;  
  13. 我們來測試下,  
  14. mysql> insertinto test (url) values("www.hao123.com");  
  15. Query OK, 1 rowaffected (0.01 sec)  
  16.    
  17. mysql> select *from test;  
  18. +----+----------------+------------+  
  19. | id | url            | url_crc    |  
  20. +----+----------------+------------+  
  21. |  1 | www.hao123.com | 3883448495 |  
  22. +----+----------------+------------+  
  23. 1 row in set (0.00sec)  

 

如果採用這種方式,記住不要使用SHA1()和MD5()作爲哈希函數。因爲這兩個值計算出來的哈希值是非常長的字符串,會浪費大量時間,比較時也會比較慢。

在出現哈希衝突時,必須在子句中包含常量值。

 

[sql] view plain copy

  1. mysql> select *from test where url_crc=crc32("www.hao123.com") andurl="www.hao123.com";  
  2. +----+----------------+------------+  
  3. | id | url            | url_crc    |  
  4. +----+----------------+------------+  
  5. |  1 | www.hao123.com | 3883448495 |  
  6. +----+----------------+------------+  
  7. 1 row in set (0.00sec)  

 

Mysql常見的索引:主鍵索引、唯一索引、普通索引、全文索引、組合索引

唯一索引

與普通索引類似,不同的就是:索引列的值必須唯一,但允許有空值(注意和主鍵不同)。如果是組合索引,則列值的組合必須唯一。

例如,在已經存好數據的表中添加唯一索引,如果值有重複會報錯,

 

[sql] view plain copy

  1. <span style="font-weight: normal;">mysql> altertable newcity  
  2.     -> add unique idx_u (Name);  
  3. ERROR 1062(23000): Duplicate entry 'San Jose' for key 'idx_u'</span>  

主鍵索引

它是一種特殊的唯一索引,不允許有空值。

 

組合索引:

平時用的SQL查詢語句一般都有比較多的限制條件,所以爲了進一步榨取MySQL的效率,就要考慮建立組合索引。

 

[sql] view plain copy

  1. mysql> alter table newcity  
  2.     -> add index idx(CountryCode,District,Name);  
  3. Query OK, 0 rows affected (0.12 sec)  
  4. Records: 0  Duplicates: 0  Warnings: 0  

相當於同時創建了三個索引,

(ConutryCode,District,Name),(CountryCode,District),(CountryCode)。

這是因爲從最左開始組合的。所以依次生成了三個索引。

全文索引

      在前面描述中,在B-tree中可以通過列前綴進行查詢。例如

 

[sql] view plain copy

  1. select * from testwhere body=”hello%”;  

 

然而,我們更普遍的查找方式是,

 

[sql] view plain copy

  1. select * from testwhere body=”%hello%”;  

 

全文索引可以支持各種字符在內的搜索,也支持自然語言搜索和布爾搜索

創建全文索引如下:

 

[sql] view plain copy

  1. mysql> createtable fts(  
  2.     -> id int unsigned auto_increment,  
  3.     -> body text,  
  4.     -> primary key(id)  
  5.     -> );  
  6. Query OK, 0 rowsaffected (0.03 sec)  

 

注意,在innodb存儲引擎中爲了支持全文索引,必須有一列與word經行映射。在Innodb中這個列別命名爲FTS_DOC_ID,其類型必須是BIGINT UNSIGNED NOT NULL,並且innodb存儲引擎自動會在該列上加入一個名爲FTS_DOC_ID_INDEX的unique index。上述操作都是由Innodb存儲引擎自己完成的,用戶也可在建表時手動添加FTS_DOC_ID以及相應的Unique idnex。

插入數據

 

[sql] view plain copy

  1. insert into fts(body) values (“hello world”);  
  2. …  
  3. mysql> select *from fts;  
  4. +----+-----------------------------+  
  5. | id | body                        |  
  6. +----+-----------------------------+  
  7. |  1 | hello world                 |  
  8. |  2 | hello ptyhon                |  
  9. |  3 | python and flask and django |  
  10. |  4 | flask and django web        |  
  11. +----+-----------------------------+  
  12. 4 rows in set(0.00 sec)  

 

建立全文索引

 

[sql] view plain copy

  1. mysql> createfulltext index idx on fts(body);  

 

通過設置參數innodb_ft_aux_table來查看分詞對應的信息

 

[sql] view plain copy

  1. mysql> setglobal innodb_ft_aux_table="world/fts";  
  2. Query OK, 0 rowsaffected (0.00 sec)  
  3. mysql> select *from information_schema.INNODB_FT_INDEX_TABLE;  
  4. +--------+--------------+-------------+-----------+--------+----------+  
  5. | WORD   | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT |DOC_ID | POSITION |  
  6. +--------+--------------+-------------+-----------+--------+----------+  
  7. | and    |           5 |           6 |         2 |      5 |       7 |  
  8. | and    |           5 |           6 |         2 |      5 |      10 |  
  9. | and    |           5 |           6 |        2 |      6 |        6 |  
  10. | django |            5 |           6 |         2 |      5 |      21 |  
  11. | django |            5 |           6 |         2 |      6 |      10 |  
  12. | flask  |           5 |           6 |         2 |      5 |      11 |  
  13. | flask  |           5 |           6 |         2 |      6 |       0 |  
  14. | hello  |           3 |           4 |         2 |      3 |       0 |  
  15. | hello  |           3 |           4 |         2 |      4 |       0 |  
  16. | ptyhon |            4 |           4 |         1 |     4 |        6 |  
  17. | python |            5 |           5 |         1 |      5 |       0 |  
  18. | web    |           6 |           6 |         1 |      6 |      17 |  
  19. | world  |           3 |           3 |         1 |      3 |       6 |  
  20. +--------+--------------+-------------+-----------+--------+----------+  
  21. 13 rows in set(0.00 sec)  

 

可以看出每一個word都對應一個DOC_ID和POSITION。此外還記錄了FIRST_ID,LAST_DOC_ID以及DOC_COUNT,分別代表了word第一次出現的文檔ID,最後一次出現的文檔ID,以及word在在多少個文檔中存在。

全文索引的自然語言索引

自然語言索引引擎將計算每一個文檔對象和查詢的相關度。這裏,相關度是指基於匹配的關鍵詞個數,以及關鍵詞在文檔中出現的個數。

自然語言索引是默認的。

函數match()將返回關鍵詞匹配的相關度,是一個浮點數字。

在match()中指定的列必須和全文索引中指定的列完全相同,否則無法只用全文索引。

舉例子

 

[sql] view plain copy

  1. mysql> select id,body,match(body)against("django") as rf from fts where match(body)against("django");  
  2. +----+-----------------------------+--------------------+  
  3. | id | body                        | rf                 |  
  4. +----+-----------------------------+--------------------+  
  5. |  3| python and flask and django | 0.0906190574169159 |  
  6. |  4| flask and django web        |0.0906190574169159 |  
  7. +----+-----------------------------+--------------------  

 

Boolen全文索引

Mysql數據庫允許使用in boolen model修飾符來經行全文檢索。當使用該修飾符時,查詢字符串前後字符會有特殊含義。

Boolen全文檢索支持以下幾種操作符:

l  +:表示word必須存在

l  -:表示word必須不存在

 

[sql] view plain copy

  1. mysql> select *from fts where match(body) against("+django -python" IN BOOLEANMODE);  
  2. +----+----------------------+  
  3. | id | body                 |  
  4. +----+----------------------+  
  5. |  4 | flask and django web |  
  6. +----+----------------------+  
  7. 1 row in set (0.00sec)  

 

l  (no operator) 表示word是可選的。但是如果是可選的,其相關性會更高。

 

[sql] view plain copy

  1. mysql> select *,match(body) against("flask >python <django" in boolean mode) as rf from fts;  
  2. +----+-----------------------------+---------------------+  
  3. | id | body                        | rf                  |  
  4. +----+-----------------------------+---------------------+  
  5. |  1 | hello world                 |                   0 |  
  6. |  2 | hello ptyhon                |                   0 |  
  7. |  3 | python and flask and django |  0.5437143445014954 |  
  8. |  4 | flask and django web        | -0.8187618255615234 |  
  9. +----+-----------------------------+---------------------+  
  10. 4 rows in set (0.00 sec)  

 

l  @distance表示查詢的多個單詞之間的距離是否在distance之內。                               

 

[sql] view plain copy

  1. mysql> select *from fts where match(body) against('"django flask"@3' IN BOOLEANMODE);  
  2. +----+-----------------------------+  
  3. | id | body                        |  
  4. +----+-----------------------------+  
  5. |  3 | python and flask and django |  
  6. |  4 | flask and django web        |  
  7. +----+-----------------------------+  

 

2 rows in set(0.00 sec)

l  >表示出現該word增加相關性

l  <表示出現該word降低相關性

 

[sql] view plain copy

  1. mysql> select*,match(body) against(">web"IN BOOLEAN MODE) as rf from fts;  
  2. +----+-----------------------------+--------------------+  
  3. | id | body                        | rf                 |  
  4. +----+-----------------------------+--------------------+  
  5. |  1 | hello world                 |                  0 |  
  6. |  2 | hello ptyhon                |                  0 |  
  7. |  3 | python and flask and django |                  0 |  
  8. |  4 | flask and django web        | 1.3624762296676636 |  
  9. +----+-----------------------------+--------------------+  

 

l  ~允許出現該單詞,但是出現時相關性爲負。

l  *表示以該單詞開頭的單詞,如lik*,表示可以是like,likes和lik

 

[sql] view plain copy

  1. mysql> select *from fts where match(body) against("p*" IN BOOLEAN MODE);  
  2. +----+-----------------------------+  
  3. | id | body                        |  
  4. +----+-----------------------------+  
  5. |  2 | hello ptyhon                |  
  6. |  3 | python and flask and django |  
  7. +----+-----------------------------+  
  8. 2 rows in set(0.00 sec)  

 

l  “表示短語

 

[sql] view plain copy

  1. mysql> select *from fts where match(body) against('hello world'IN BOOLEAN MODE);  
  2. +----+--------------+  
  3. | id | body         |  
  4. +----+--------------+  
  5. |  1 | hello world  |  
  6. |  2 | hello ptyhon |  
  7. +----+--------------+  
  8. 2 rows in set(0.00 sec)  
  9.    
  10. mysql> select *from fts where match(body) against('"hello world"'IN BOOLEAN MODE);  
  11. +----+-------------+  
  12. | id | body        |  
  13. +----+-------------+  
  14. |  1 | hello world |  
  15. +----+-------------+  
  16. 1 row in set (0.00sec)  

 

注意到,第一個against(“hello world“)是將hello和world當作兩個單詞經行查詢。而第二個against(‘”hello world“’)是將這兩個單詞當作短語經行查詢。

 

 

 

索引區別

 

普通索引:最基本的索引,沒有任何限制
唯一索引:與"普通索引"類似,不同的就是:索引列的值必須唯一,但允許有空值。
主鍵索引:它是一種特殊的唯一索引,不允許有空值。 
全文索引:針對較大的數據,生成全文索引很耗時好空間。
組合索引:爲了更多的提高mysql效率可建立組合索引,遵循”最左前綴“原則。

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