Mysql專題三:B+樹索引實戰

一、索引的代價

1、空間上的代價
一個索引都爲對應一棵B+樹,樹中每一個節點都是一個數據頁,一個頁默認會佔用16KB的存儲空間,所以一個索引也是會佔用磁盤空間的。

2、時間上的代價
索引是對數據的排序,那麼當對錶中的數據進行增、刪、改操作時,都需要去維護修改內容涉及到的B+樹索引。所以在進行增、刪、改操作時可能需要額外的時間進行一些記錄移動,頁面分裂、頁面回收等操作來維護好排序。

二、B+樹索引實戰

由於是基於前面數據進行實戰,請查看前面的專題;
聯合索引爲bcd,下面情況均基於bcd聯合索引的場景下

  • 1、全值匹配
select * from t1 where b = 1 and c = 1 and d = 1;

查詢優化器會分析這些查詢條件並且按照可以使用的索引中列的順序來決定先使用哪個查詢條件。

  • 2、匹配左邊的列
    最左前置原則
select * from t1 where b = 1;
select * from t1 where b = 1 and c = 1;

下面這個sql是用不到索引的

select * from t1 where c = 1;

因爲B+樹先是按照b列的值排序的,在b列的值相同的情況下才使用c列進行排序,也就是說b列的值不同的記錄中c的值可能是無序的。而現在你跳過b列直接根據c的值去查找,這是做不到的。

  • 3、匹配列前綴
    如果只給出後綴或者中間的某個字符串,比如:
select * from t1 where b like '%101%';

這種是用不到索引的,因爲字符串中間有’101’的字符串並沒有排好序,所以只能全表掃描了。有時候我們有一些匹配某些字符串後綴的需求,比方說某個表有一個url列,該列中存儲了許多url:

www.baidu.com 
www.google.com 
www.qq.com

假設已經對該url列創建了索引,如果我們想查詢以com爲後綴的網址的話可以這樣寫查詢條件:WHERE url LIKE ‘%com’,但是這樣的話無法使用該url列的索引。
爲了在查詢時用到這個索引而不至於全表掃描,我們可以把後綴查詢改寫成前綴查詢,不過我們就得把表中的數據全部逆序存儲一下,也就是說我們可以這樣保存url列中的數據:

moc.udiab.www 
moc.elgoog.www
moc.qq.www
  • 4、匹配範圍值
select * from t1 where b > 1 and b < 20000;

由於B+樹中的數據頁和記錄是先按b列排序的,所以我們上邊的查詢過程其實是這樣的:

找到b值爲1的記錄。
找到b值爲20000的記錄。
由於所有記錄都是由鏈表連起來的(記錄之間用單鏈表,數據頁之間用雙鏈表),所以他們之間的記錄都可以很容易的取出來
找到這些記錄的主鍵值,再到聚簇索引中回表查找完整的記錄。

不過在使用聯合進行範圍查找的時候需要注意,如果對多個列同時進行範圍查找的話,只有對索引最左邊的那個列進行範圍查找的時候才能用到B+樹索引,比如:

select * from t1 where b > 1 and c > 1;

上邊這個查詢可以分成兩個部分:
1)通過條件b > 1來對b進行範圍,查找的結果可能有多條b值不同的記錄;
2)對這些b值不同的記錄繼續通過c > 1繼續過濾。
這樣子對於聯合索引來說,只能用到b列的部分,而用不到c列的部分,因爲只有b值相同的情況下才能用c列的值進行排序,而這個查詢中通過b進行範圍查找的記錄中可能並不是按照c列進行排序的,所以在搜索條件中繼續以c列進行查找時是用不到這個B+樹索引的。

  • 5、精確匹配某一列並範圍匹配另外一列

對於同一個聯合索引來說,雖然對多個列都進行範圍查找時只能用到最左邊那個索引列,但是如果左邊的列是精確查找,則右邊的列可以進行範圍查找,比方說這樣:

select * from t1 where b = 1 and c > 1;
  • 6、排序
select * from t1 order by b, c, d;

這個查詢的結果集需要先按照b值排序,如果記錄的b值相同,則需要按照c來排序,如果c的值相同,則需要按照d排序。因爲這個B+樹索引本身就是按照上述規則排好序的,所以直接從索引中提取數據,然後進行回表操作取出該索引中不包含的列就好了。

  • 7、分組
select b, c, d, count(*) from t1 group by b, c, d;

這個查詢語句相當於做了3次分組操作:
1)先把記錄按照b值進行分組,所有b值相同的記錄劃分爲一組。
2)將每個b值相同的分組裏的記錄再按照c的值進行分組,將title值相同的記錄放到一個分組裏。
3)再將上一步中產生的分組按照d的值分成更小的分組。

如果沒有索引的話,這個分組過程全部需要在內存裏實現,而如果有索引的話,正好這個分組順序又和B+樹中的索引列的順序是一致的,所以可以直接使用B+樹索引進行分組。

  • 8、使用聯合索引進行排序或分組的注意事項

對於聯合索引有個問題需要注意,ORDER BY的子句後邊的列的順序也必須按照索引列的順序給出,如果給出order by c, b, d的順序,那也是用不了B+樹索引的。
同理,order by b,order by b, c這種匹配索引左邊的列的形式可以使用部分的B+樹索引。當聯合索引左邊列的值爲常量,也可以使用後邊的列進行排序,比如這樣:

select * from t1 where b = 1 order by c, d;
  • 9、不可以使用索引進行排序或分組的幾種情況

ASC、DESC混用
對於使用聯合索引進行排序的場景,我們要求各個排序列的排序順序是一致的,也就是要麼各個列都是ASC規則排序,要麼都是DESC規則排序。
ORDER BY子句後的列如果不加ASC或者DESC默認是按照ASC排序規則排序的,也就是升序排序的。

select * from t1 order by b ASC, c DESC;

這個查詢是用不到索引的。

三、如何建立索引

  • 1、考慮索引選擇性

索引的選擇性(Selectivity),是指不重複的索引值(也叫基數,Cardinality)與表記錄數的比值:

	選擇性 = 基數 / 記錄數

選擇性的取值範圍爲(0, 1],選擇性越高的索引價值越大。如果選擇性等於1,就代表這個列的不重複值和表記錄數是一樣的,那麼對這個列建立索引是非常合適的,如果選擇性非常小,那麼就代表這個列的重複值是很多的,不適合建立索引。

  • 2、考慮前綴索引

用列的前綴代替整個列作爲索引key,當前綴長度合適時,可以做到既使得前綴索引的選擇性接近全列索引,同時因爲索引key變短而減少了索引文件的大小和維護開銷。

	使用mysql官網提供的示例數據庫:https://dev.mysql.com/doc/employee/en/employees-installation.html
	github地址:https://github.com/datacharmer/test_db.git

employees表只有一個索引<emp_no>,那麼如果我們想按名字搜索一個人,就只能全表掃描了:查詢列沒有索引

EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido';

那麼可以對<first_name>或<first_name, last_name>建立索引,看下兩個索引的選擇性:

SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees; -- 0.0042
SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees; -- 0.9313

<first_name>顯然選擇性太低,<first_name, last_name>選擇性很好,但是first_name和last_name加起來長度爲30,有沒有兼顧長度和選擇性的辦法?可以考慮用first_name和last_name的前幾個字符建立索引,例如<first_name, left(last_name, 3)>,看看其選擇性:

SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees; -- 0.7879

選擇性還不錯,但離0.9313還是有點距離,那麼把last_name前綴加到4:

SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees; -- 0.9007

這時選擇性已經很理想了,而這個索引的長度只有18,比<first_name, last_name>短了接近一半,建立前綴索引的方式爲:

ALTER TABLE employees.employees ADD INDEX `first_name_last_name4` (first_name, last_name(4));

前綴索引兼顧索引大小和查詢速度,但是其缺點是不能用於ORDER BY和GROUP BY操作,也不能用於覆蓋索引

五、總結

• 索引列的類型儘量小
• 利用索引字符串值的前綴
• 主鍵自增
• 定位並刪除表中的重複和冗餘索引
• 儘量使用覆蓋索引進行查詢,避免回錶帶來的性能損耗。

以上信息來我在魯班學院學習的資料,魯班學院很不錯,值得報班

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