MySQL索引優化

目錄

1 B-Tree索引

2 哈希索引

3 索引策略

3.1 獨立的列

3.2 使用索引掃描來做排序

3.3 索引的取捨


1 B-Tree索引

假設有如下數據表:

CREATE TABLE People (
last_name VARCHAR ( 50 ) NOT NULL,
first_name VARCHAR ( 50 ) NOT NULL,
dob date NOT NULL,
gender enum ( 'm', 'f' ) NOT NULL,
KEY ( last_name, first_name, dob ) 
);

B-Tree對索引列是順序組織存儲的,所其對如下類型的查詢有效:

  • 全值匹配
    • 全值匹配指的是和索引中的所有列進行匹配。
  • 匹配最左前綴
    • 可用於查找所有姓爲Allen的人,即指使用索引的第一列。
  • 匹配列前綴
    • 也可以只匹配某一列的值的開頭部分。例如可用於查找所有以J開頭的姓的人。這裏也只使用了索引的第一列。
  • 匹配範圍值
    • 例如可用於查找姓在Allen和Barrymore之間的人。這裏也只使用了索引的第一列。
  • 精確匹配某一列並範圍匹配另外一列
    • 可用於查找所有姓爲Allen,並且名字是字母K開頭的人。即第一列last_name全匹配,第二列first_name範圍匹配。
  • 只訪問索引的查詢
    • B-Tree通常可以支持“只訪問索引的查詢”,即查詢只需要訪問索引,而無需訪問數據行,即“覆蓋索引”。

因爲索引樹中的節點是有序的,所以除了按值查找之外,索引還可以用於查詢中的ORDER BY操作。但是使用B-Tree索引也會有一些限制:

  • 如果不是按照索引的最左列開始查找,則無法使用索引。
  • 不能跳過索引中的列。
  • 如果查詢中有某個列的範圍查詢,則其右邊所有列都無法使用索引優化查找。例如有查詢如下:
    SELECT
    	* 
    FROM
    	people 
    WHERE
    	last_name = 'Hou' 
    	AND first_name LIKE 'R%' 
    	AND dob = '1995-02-15'
    這個查詢只能使用索引的前兩列,因爲這裏LIKE是一個範圍條件。這裏描述的基本原則是:儘可能將需要做範圍查詢的列放到索引的後面,以便優化器能使用儘可能多的索引列。

2 哈希索引

哈希索引基於哈希表實現,只有精確匹配索引所有列的查詢纔有效。對於每一行數據,存儲引擎都會對所有的索引列計算一個哈希碼,哈希碼是一個較小的值,並且不同鍵值的行計算出來的哈希碼也不一樣。哈希索引將所有的哈希碼存儲在索引中,同時在哈希表中保存指向每個數據行的指針。因爲索引自身只需存儲對應的哈希值,所以索引的結構十分緊湊,這也讓哈希索引查找的速度非常快。然而,哈希索引也有它的限制:

  • 哈希索引數據並不是按照索引值順序存儲的,所以也就無法用於排序。
  • 哈希索引也不支持部分索引列匹配查找,因爲哈希索引始終是使用索引列的全部內容來計算哈希值的。例如,在數據列(A,B)上建立哈希索引,如果查詢只有數據列A,則無法使用該索引。
  • 哈希索引只支持等值比較查詢,包括=、IN()、<=>。也不支持任何範圍查詢,例如WHERE price > 100。
  • 如果哈希衝突很多的話,一些索引維護操作的代價也會很高。例如,如果在某個選擇性很低(哈希衝突很多)的列上建立哈希索引,那麼當從表中刪除一行時,存儲引擎需要遍歷對應哈希值的鏈表中的每一行,找到並刪除對應行的引用,衝突越多,代價越大。

3 索引策略

3.1 獨立的列

我們通常會看到一些查詢不當地使用索引,或者使得MySQL無法使用已有的索引。如果查詢中的列不是獨立的,則MySQL就不會使用索引。“獨立的列”是指索引列不能是表達式的一部分,也不能是函數的參數。如下所示:

SELECT
	actor_id 
FROM
	sakila.actor 
WHERE
	actor_id + 1 = 5;
SELECT
	...
WHERE
	TO_DAYS( CURRENT_DATE ) - TO_DAYS( date_col ) <= 10;

3.2 使用索引掃描來做排序

只有當索引的列順序和ORDER BY子句的順序完全一致,並且所有列的排序方向(倒序或正序)都一樣時,MySQL才能夠使用索引來對結果做排序。如果查詢需要關聯多張表,則只有當ORDER BY子句引用的字段全部爲第一個表時,才能使用索引做排序。ORDER BY子句和查找型查詢的限制是一樣的:需要滿足索引的最左前綴的要求;否則,MySQL都需要執行排序操作,而無法利用索引排序。

有一種情況下ORDER BY子句可以不滿足索引的最左前綴的要求,就是前導列爲常量的時候。如果WHERE子句或者JOIN子句中對這些列指定了常量,就可以“彌補”索引的不足。例如下面的索引和對應的SQL:

UNIQUE KEY rental_date ( rental_date, inventory_id, customer_id ),
SELECT
	rental_id,
	staff_id 
FROM
	sakila.rental 
WHERE
	rental_date = '2029-02-29' 
ORDER BY
	inventory_id,
	customer_id

3.3 索引的取捨

一些列的選擇性通常不高,但可能很多查詢都會用到,所以考慮到使用的頻率,還是建議在創建不同組合索引的時候將該列作爲前綴。但這違背了不應該在選擇性低的列上創建索引的經驗,但是可以通過下面的“訣竅”繞過:假如說該列是性別列SEX,如果某個查詢不限制性別,那麼可以通過在查詢條件中新增AND SEX IN ( 'm', 'f' )來讓MySQL選擇該索引。這樣寫並不會過濾任何行,和沒有這個條件時的結果相同。但是必須加上這個列的條件,MySQL才能夠匹配索引的最左前綴。這個“訣竅”在這類場景中非常有效,但如果列有太多不同的值,就會讓IN ()列表太長,這樣做就不行了。

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