MySQL索引一(B+Tree)



一:索引的類型mysql

二:索引的優勢sql

三:高性能索引策略bash

四:索引案例服務器



1.1類型介紹數據結構

索引有不少類型,能夠爲不一樣場景提供更好的性能。在MySQL中,索引是在存儲引擎層而不是服務器層實現的。因此,並無統一的索引標準:不一樣存儲引擎的索引的工做方式並不同,也不是全部的存儲引擎都支持全部類型的索引,即便多個存儲引擎支持同一種類型的索引,其底層的實現也可能不一樣ide

 

 

1.2 B+Tree索引函數

存儲引擎以不一樣的方式使用B+Tree索引,性能也各有不一樣,各有優劣。性能

例如MyISAM使用前綴壓縮技術使得索引更小,但InnoDB則按照原數據格式進行存儲。優化

再如MyISAM索引經過數據的物理位置引用被索引行,而InooDB則根據主鍵引用被索引的行spa



1.2.1 B+Tree索引數據結構

wKiom1WSEQyjIGKwAADjBorWus0298.jpg




1.2.2理解數據結構

假若有以下數據表:

CREATE TABLE people(
   last_name         varchar(50)         not null,
   first_name        varchar(50)         not null,
   dob                date                not null,
   gender            eum('m','f')        not null,
   key(last_name, first_name, dob)
)

對於每一行的數據,索引中包含了,last_name,first_name和dob列的值

wKiom1WSEWfBkvpuAAF1p7iERDw383.jpg

注意:索引對多個值進行排序的依據是CREATE TABLE語句中定義索引時列的順序。看最後兩個條目,兩我的的姓和名都同樣,則根據他們的出生日期來排列順序




1.2.3 B+Tree索引對以下類型的查詢有效

 

全值匹配:和索引中的全部列進行匹配,例如前面建立的索引可用於查找姓名爲Allen Cuba,出生於1960-01-01的人

 

匹配最左前綴:前面建立的索引可用於查找全部姓爲Allen的人,即只使用索引的第一列

 

匹配列前綴:前面建立的索引也能夠只匹配某一列的值的開頭部分,例如能夠用於查找全部以J開頭的姓的人

 

匹配範圍值:前面建立的索引可用於查找姓在Allen和Barrymore之間的人。(這裏只使用了索引的第一列

 

精確匹配某一列並範圍匹配另一列:前面建立的索引可用於查找全部姓爲Allen,而且名字是字母K開頭(好比Kim)的人。即第一列last_name全匹配,第二列first_name範圍匹配

 

只訪問索引的查詢:B+Tree一般能夠支持「只訪問索引的查詢」,即查詢只須要訪問索引,而無須訪問數據行。



由於索引樹中的節點是有序的,因此除了按值查找以外,索引還能夠用於查詢中的ORDER BY操做。通常來講,若是B+Tree能夠按照某種方式查找到值,那麼也能夠按照這種方式用於排序。因此,若是ORDER BY 子句知足前面列出的幾種查詢類型,則這個索引也能夠知足對應的排序需求




1.2.4 B+Tree索引的限制

 

1.2.4.1若是不是按照索引的最左列開始查找,則沒法使用索引。例如上面例子中的索引沒法用於查找名字爲Kim的人,也沒法查找某個特定生日的人,由於這兩列都不是最左數據列。相似的,也沒法查找姓氏以某個字母結尾的人

 

1.2.4.2不能跳過索引中的列。也就是說,前面所述的索引沒法用於查找姓爲Allen而且在某個特定日期出生的人。若是不指定名(first_name),則MySQL只能使用索引的第一列

 

1.2.4.3若是查詢中有某個列的範圍查詢,則其右邊全部列都沒法使用索引優化查找。例若有查詢WHERE last_name='Allen' ANDfirst_name LIKE 'K%' AND dob='1930-07-12',這個查詢只能使用索引的前兩列,由於這裏LIKE是一個範圍條件(可是服務器能夠把其他列用於其它目的)。若是範圍查詢列值數量有限,那麼能夠經過使用多個等於條件來代替範圍條件。儘量將範圍查詢放到最後,由於範圍查詢後的列不能使用索引




2.1索引的優勢

索引可讓服務器快速的定位到表的指定位置。可是這並非索引的惟一做用,到目前爲止

 

能夠看到,根據建立索引的數據結構不一樣,索引也有一些其它的附加做用

最多見的B+Tree索引,按照順序存儲數據,因此MySQL能夠用來作ORDER BY 和GROUPBY操做。由於數據是有序的,因此B+Tree也就會將相關的列值都存儲在一塊兒。最後,由於索引中存儲了實際的列值,因此某些查詢只使用索引就可以完成所有查詢。據此特性,總結下來索引有以下三個優勢:

1.索引大大減小了服務器須要掃描的數據量

2.索引能夠幫助服務器避免排序和臨時表

3.索引能夠將隨機I/O變爲順序I/O




Lahdenmaki和Leach提出,如何評價一個索引是否適合某個查詢的「三星系統」:

A.索引將相關的記錄放到一塊兒則得到一星;

B.若是索引中的數據順序和查找中的排序順序一致則得到兩星;

C.若是索引中的列包含了查詢中須要的所有列則得到三星;




3.高性能的索引策略

正確的建立索引和使用索引是實現高性能查詢的基礎。如今討論一下如何真正的發揮這些索引的優點

 

3.1獨立的索引

一般看到一些查詢不當的使用索引,或者使MySQL沒法使用已有的索引。若是查詢中的列不是獨立的,則MySQL就不會使用索引。「獨立的列」是指索引列不能是表達式的一部分,也不能是函數的參數

 

例1:下面這個查詢就沒法使用user_id列的索引

mysql> SELECT user_id FROM tb1 WHERE user_id+1 = 5;

很容易看出WHERE中的表達式起始等價於user_id=4,可是MySQL沒法自動解析這個方程式,這徹底是用戶行爲。咱們應該養成簡化WHERE條件的習慣,始終將索引列單獨放在比較符號的一側



例2:下面這個查詢就沒法使用date_col列的索引

mysql> SELECT ... WHERE TO_DAYS(CUURRENT_DATE) - TO_DAYS(date_col) <= 10

解決方法:在程序中獲取當前時間,減去10天,加入最後的值存儲在last_date_val中

mysql> SELECT ... WHERE date_col <= last_date_val



3.2前綴索引和索引選擇性

有時候須要索引很長的字符列,這會讓索引變得很慢。一般能夠索引開始的部分字符,這樣能夠大大節約索引空間,從而提升索引效率。但這樣也會下降索引的選擇性。


索引選擇性是指:不重複的索引值(也稱爲基數)和數據表的記錄總數(#T)的比值,範圍從1/#T到1之間。索引的選擇性越高,則查詢效率越高,由於選擇性高的索引可讓MySQL在查找時過濾掉更多的行。惟一索引的選擇性是1,這是最好的索引選擇性,性能也最好

 

理解索引選擇:每行記錄都有一個索引值,若是前綴過少,那麼索引值重複的可能性就越大,這樣在查詢的時候查詢到的數據就越多,過濾掉的數據就越少,因此要找一個合理的前綴。

 

合理前綴的訣竅在於:選擇足夠長的前綴以保證較高的選擇性,同時又不能太長(以便節約空間)。前綴應該足夠長,以使得前綴索引的選擇性接近於索引整個列。換句話說,前綴的「基數」應該接近於完整列的「基數」



3.2.1如何選擇合理前綴的長度

mysql> CREATE TABLE tb( 
    -> city VARCHAR(50) NOT NULL 
    -> ); 
Query OK, 0 rows affected (0.26 sec)


mysql> SELECT COUNT(*) AS cnt, city_name FROM city GROUP BY city_name  
       ORDER BY cnt DESC LIMIT 10;

wKiom1WSExGRd5CCAACrdQakpA4784.jpg


能夠看到,上面每一個值都出現了5-7次,如今查找到最頻繁出現的城市,先從3個前綴字母開始



mysql> SELECT COUNT(*) AS cnt, LEFT(city_name,3) AS pref FROM city  
     GROUP BY pref ORDER BY cnt DESC LIMIT 10;

wKiom1WSE0exPCeEAACEcYYoOCk265.jpg

每一個前綴都比原來的城市出現的次數更多,所以惟一前綴比惟一城市要少的多。而後增長前綴的長度,知道這個前綴的選擇性接近完整列的選擇性。


mysql> SELECT COUNT(*) AS cnt, LEFT(city_name,7) AS pref FROM city 
      GROUP BY pref ORDER BY cnt DESC LIMIT 10;

wKioL1WSFW7x24VIAACGzV-o-A8032.jpg



計算合適前綴的長度的方法就是計算完整列的選擇性,並使前綴的選擇性接近於完整列的選擇性。下面是如何計算完整列的選擇性:


mysql> SELECT COUNT(DISTINCT city_name)/COUNT(*) FROM city;

wKiom1WSE9aR2ytSAACnZqRYNFs678.jpg

一般來講(儘管也有例外狀況),這個例子中若是前綴的選擇性可以接近於0.4317,基本上就可用了,能夠在一個查詢中針對不一樣前綴長度計算。

wKioL1WSFcHBWKvjAAHhOPOz4Ao061.jpg

顯示長度爲7的時候,再增長前綴長度,選擇性已經沒有提高了(或者某些時候提高幅度很小了)




3.2.2建立前綴索引

mysql> ALTER TABLE city ADD [KEY|INDEX] city_name_index(city_name(7));

前綴索引是一種能使索引更小、更快的有效方法,但另外一方面也有其缺點:MySQL沒法使用前綴索引作ORDER BY 和 GROUP BY ,也就沒法使用前綴索引作覆蓋掃描




3.3多列索引

不少人對多列索引的理解還不夠。一個常見的錯誤就是,爲每一個列建立獨立的索引,或者按照錯誤的順序建立多列索引

CREATE TABLE t(
    c1 INT,
    c2 INT,
    c3 INT,
    KEY(c1),
    KEY(c2),
    KEY(c3)
);

這種索引策略,通常是因爲人們聽到一些說,「把WHERE條件裏面的列都建上索引」致使的。其實是很是錯誤的,這樣一來最好的狀況下也只能是「一星」索引,其性能比起真正最有的索引可能差幾個數量級。有時候若是沒法設計一個「三星」索引,那麼不如忽略掉WHERE子句,集中精力優化索引列的順序,或者建立一個全覆蓋索引

 

 

在多個列上創建獨立的單列索引大部分狀況下並不能提升MySQL的查詢性能。MySQL5.0和更新版本引入了一種叫「索引合併」的策略,必定程度上可使用表上的多個單列索引來定位指定的行。

 

更早版本的MySQL只能使用其中某一個單列索引,而後這種狀況下沒有哪個獨立的單列索引是很是有效的。例如:表film_actor在字段film_id和actor_id上各有一個單列索引,但對於下面這個查詢WHERE條件,這兩個單列索引都不是好的選擇


mysql> SELECT film_id,actor_id FROM film_actor WHERE actor_id=1OR film_id=1;

在老的MySQL版本中,MySQL對這個查詢會使用全表掃描。除非改寫成以下的兩個查詢。

mysql> SELECT film_id,actor_id FROM film_actor WHERE actor_id=1  
-> UNION ALL 
-> SELECT film_id,actor_id FROM film_actor WHERE film_id=1 AND actor_id<>1;




3.4選擇合適的索引列順序

3.4.1例子分析

mysql> SELECT * FROM payment WHERE staff_id = 2 AND customer_id = 584;

是建立一個(staff_id, customer_id)索引仍是應該顛倒順序呢?能夠作一些查詢來肯定這個表中值的分佈狀況,並肯定哪一個列的選擇性更高。

mysql> SELECT SUM(staff_id=2),SUM(customer_id=584) FROM payment;

wKiom1WSLdzSk0RpAADdFqYfkxg181.jpg

mysql> SELECT COUNT(DISTINCT(staff_id))/COUNT(*) AS staff_id ,
              COUNT(DISTINCT(customer_id))/COUNT(*) AS customer_id ,
              COUNT(*) 
      FROM payment;

wKiom1WSLhiy_4SQAADxTNmsqYQ085.jpg







wKiom1WSLwKx5lvLABL1dzOLnB4167.jpg

相關文章
相關標籤/搜索