Mysql探索(一):B-Tree索引

圖片描述

 MySQL是目前業界最爲流行的關係型數據庫之一,而索引的優化也是數據庫性能優化的關鍵之一。因此,充分地瞭解MySQL索引有助於提高開發人員對MySQL數據庫的使用優化能力。
 MySQL的索引有不少種類型,能夠爲不一樣的場景提供更好的性能。而B-Tree索引是最爲常見的MySQL索引類型,通常談論MySQL索引時,若是沒有特別說明,就是指B-Tree索引。本文就詳細講解一下B-Tree索引的的底層結構,使用原則和特性。
 爲了節約你的時間,本文的主要內容以下:html

  • B-Tree索引的底層結構
  • B-Tree索引的使用規則
  • 聚簇索引
  • InnoDB和MyISAM引擎索引的差別
  • 鬆散索引
  • 覆蓋索引

B-Tree索引

 B-Tree索引使用B-Tree來存儲數據,固然不一樣存儲引擎的實現方式不一樣。B-Tree一般意味着全部的值都是按順序存儲的,而且每個葉子頁到根的距離相同,圖1展現了B-Tree索引的抽象表示,由此能夠看出MySQL的B-Tree索引的大體工做機制。mysql

 B-Tree索引的底層數據結構通常是B+樹,其具體數據結構和優點這裏就不做詳細描述,圖1展現了B-樹索引的抽象表示,大體反應了MyISAM索引是如何工做的,而InnoDB使用的結構有所不一樣。算法

圖1 B-Tree索引的底層結構示意圖

 MySQL能夠在單獨一列上添加B-Tree索引,也能夠在多列數據上添加B-Tree索引,多列的數據按照添加索引聲明的順序組合起來,存儲在B-Tree的頁中。假設有以下數據表:sql

CREATE TABLE People (
      last_name    varchar(50)    not null,
      first_name   varchar(50)    not null,
      birthday     date           not null,
      gender       enum('m','f')  not null
      key(last_name, first_name, birthday)
);

 對於表中的每一行數據,索引中包含了last_name,first_name和birthday列的值,圖2展現了該索引是如何組織數據的存儲的。數據庫

圖2 多列索引

 B-Tree索引使用B-Tree做爲其存儲數據的數據結構,其使用的查詢規則也由此決定。通常來講,B-Tree索引適用於全鍵值、鍵值範圍和鍵前綴查找,其中鍵前綴查找只適用於根據最左前綴查找。B-Tree索引支持的查詢原則以下所示:性能優化

  • 全值匹配:全值匹配指的是和索引中的全部列進行匹配,
  • 匹配最左前綴:前邊提到的索引能夠用於查找全部姓Allen的人,即只使用索引中的第一列。
  • 匹配列前綴:也能夠只匹配某一列的值的開頭部分。例如前面提到的索引可用於查找全部以J開頭的姓的人。這裏也只用到了索引的第一列。
  • 匹配範圍值:例如前邊提到的索引可用於查找姓在Allen和Barrymore之間的人。這裏也只使用了索引的第一列。
  • 精確匹配某一列並範圍匹配另一列:前邊提到的索引也可用於查找全部姓爲Allen,而且名字是字母K開頭(好比Kim,Karl等)的人。即第一列last_name全匹配,第二列first_name範圍匹配。

 由於索引樹的節點是有序的,因此除了按值查找以外,索引還能夠用於查詢中的ORDER BY操做(按順序查找),若是ORDER BY子句知足前面列出的幾種查詢類型,則這個索引也能夠知足對應的排序需求。微信

 下面是一些關於B-Tree索引的限制:數據結構

  • 若是不是按照索引的最左列開始查找,則沒法使用索引。例如上面例子中的索引沒法查找名字爲Bill的人,也沒法查找某個特定生日的日,由於這兩列都不是最左數據列。
  • 若是查詢中有某個列的範圍查詢,則其右側全部列都沒法使用索引優化查找。

聚簇索引

 聚簇索引並非一種單獨的索引類型,而是一種數據存儲方式。具體的細節依賴於其實現方式,可是InnoDB的聚簇索引實際上在同一個結構中保存了B-Tree索引和數據行。工具

 當表有聚簇索引時,它的數據行實際上存放在索引的葉子頁中,這也就是說數據行和相鄰的鍵值緊湊地存儲在一塊兒。性能

 圖3展現了聚簇索引中的記錄是如何存放的。注意到,葉子頁包含了行的所有數據行,可是節點頁只包含了索引列。

圖3 聚簇索引

 聚簇索引可能對性能有幫助,但也可能致使嚴重的性能問題。聚簇的數據是有一些重要的優勢:

  • 數據訪問更快,聚簇索引將索引和數據保存在同一個B-Tree中,所以從聚簇索引中獲取數據一般比在非聚簇索引中查找要快。
  • 使用覆蓋索引掃描的查詢能夠直接使用頁節點中的主鍵值。

 若是在設計表和查詢時能充分利用上面的優勢,那麼就能極大地提高性能。同時,聚簇索引也有一些缺點:

  • 插入順序嚴重依賴插入順序。按照主鍵的順序插入是向InnoDB表中插入數據速度最快的方式,須要避免主鍵鍵值隨機的(不連續且值得分佈範圍很是大)聚簇索引,好比使用UUID做爲主鍵,而應該使用相似AUTO_INCREMENT的自增列。
  • 更新聚簇索引列的代價很高,由於會強制InnoDB將每一個被更新的行移動位置到新的位置。
  • 基於聚簇索引的表在插入新行,或者主鍵被更新致使須要移動行時,可能面臨「頁分裂」的問題。當行的主鍵值要求必須將這行插入到某個已滿的頁中時,存儲引擎會將該頁分裂成兩個頁面來容納該行,這就是一次頁分裂操做。頁分裂會致使表佔用更多的磁盤空間
  • 二級索引可能比想象的更大,由於在二級索引中的葉節點包含了引用行的主鍵列
  • 二級索引訪問須要兩次索引查找,而不是一次。

InnoDB和MyISAM的索引區別

 聚簇索引和非聚簇索引的數據分佈有區別,以及對應的主鍵索引和二級索引的數據分佈也有區別,一般會讓人感到困惑和意外。圖4展現了MyISAM和InnoDB的不一樣索引和數據存儲方式。
 MyISAM的數據分佈很是簡單,按照數據插入的順序存儲在磁盤上,主鍵索引和二級索引的葉節點存儲着指針,指向對應的數據行。
 InnoDB中,聚簇索引「就是」表,因此不會像MyISAM那樣須要獨立的行存儲。聚簇索引的每一個葉節點都包含了主鍵值和全部的剩餘列(在此例中是col2)。
 InnoDB的二級索引和聚簇索引很不一樣。InnoDB二級索引的葉節點中存儲的不是「行指針」,而是主鍵值,並以此做爲指向行的「指針」。

鬆散索引掃描

 MySQL並不支持鬆散索引掃描,也就是沒法按照不連續的方式掃描一個索引。一般,MySQL的索引掃描須要先定義一個起點和終點,即便須要的數據只是這段索引中不多數的幾個,MySQL仍然須要掃描這段索引中的每一個條目。
 下面,咱們經過一個示例說明這點,假設咱們有以下索引(a,b),有下面的查詢:

mysql>SELECT * FROM tb1 WHERE b BETWEEN 2 AND 3;

 由於索引的前導字段是列a,可是在查詢中只指定了字段b,MySQL沒法使用這個索引,從而只能經過全表掃描找到匹配的行,如圖5所示。

圖5 全表掃描

 瞭解索引的物理結構的話,不難發現還能夠有一個更快的辦法執行上面的查詢。索引的物理結構(不是存儲引擎的API)是的能夠先掃描a列第一個值對應的b列的範圍,而後再跳到a列第二個不不一樣值掃描對應的b列的範圍。圖6展現了若是由MySQL來實現這個過程會怎樣。

圖6 鬆散索引

 注意到,這時就無須再使用WHERE子句過濾,由於鬆散索引掃描已經跳過了全部不須要的記錄。
 MySQL 5.0以後的版本,在某些特殊的場景下是可使用鬆散索引掃描的,例如,在一個分組查詢中須要找到分組的最大值和最小值:

mysql> EXPLAIN SELECT actor_id, MAX(film_id)
        -> FROM sakila.film.film_actor
        -> GROUP BY actor_id;
********************************************* 1. row ***********************************
id: 1
select_type: SIMPLE
table: film_actor
type: range
possible_keys: NULL
key: PRIMARY
key_len: 2
ref: NULL
rows: 396
Extra: Using index for group-by

 在EXPLAIN中的Extra字段顯示"Using index for group-by",表示這裏將使用鬆散索引掃描。

覆蓋索引

 索引除了是一種查找數據的高效方式以外,也是一種列數據的直接獲取方式。MySQL可使用索引來直接獲取列的數據,這樣就不須要讀取數據行。若是一個索引包含全部須要查詢的字段的值,咱們就稱之爲「覆蓋索引」。
 覆蓋索引是很是有用的工具,可以極大地提升性能。SQL查詢只須要掃描索引而無需回表,會帶來不少好處:

  • 索引條目數量和大小一般遠小於數據行的條目和大小,因此若是隻須要讀取索引,那麼MySQL就會極大地減小數據訪問量。
  • 由於索引是按照列順序存儲的,因此對於I/O密集型的範圍查找會比隨機從磁盤讀取每一行數據的I/O要少的多。
  • 因爲InnoDB的聚簇索引,覆蓋索引對InnoDB表特別有用。InnoDB的二級索引在葉子節點中保存了行的主鍵,索引若是二級主鍵可以覆蓋查詢,則避免對主鍵索引的第二次查詢。

 當發起一個被覆蓋索引的查詢(也叫索引覆蓋查詢)時,在EXPLAIN的Extra列能夠看到"Using Index"的信息。例如,表sakila.inventory有一個多列索引(store_id, film_id)。MySQL若是隻須要訪問這兩列,就可使用這個索引作覆蓋索引,以下所示:

mysql> EXPLAIN SELECT store_id, film_id FROM sakila.inventory
*********************************1.row***************************************
id:1
select_type:SIMPLE
table:inventory
type:index
possible_keys:NULL
key:idx_store_id_film_id
key_len:3
ref:NULL
rows:4673
Extra:Using Index

訂閱最新文章,歡迎關注個人微信公衆號

參考:

相關文章
相關標籤/搜索