MySQL 的索引理解

索引

@(數據庫)[MySQL]html

索引是什麼?

索引的解釋

數據庫索引,是數據庫管理系統中一個排序的數據結構,以協助快速查詢,更新數據庫表中數據。
  • 值得注意的是:SQL 標準並無爲數據庫用戶提供任何在數據庫系統中控制建立和維護索引的防範,因爲索引是冗餘的數據結構,所以索引對保證正確性來講並非必須的;可是索引對事務的高效處理十分重要,對完整性約束的有效實施也很重要;若是沒有索引的話,MySQL進行全表掃描會對資源進行極大的浪費。
  • 若是不恰當的使用索引的話,只會增長無效的數據維護,耗費資源,同時查詢效率仍然得不到提升。
  • 那麼索引到底是什麼呢?索引是一種數據結構,一個索引是存儲的表中一個特定列或者某幾個特定列的值數據結構,索引是在列上建立的。

索引的分類

有兩種基本的索引類型
順序索引:基於值的順序排序
散列索引:基於將值平均分佈在若干散列桶中,一個值所屬的散列通是有一個函數決定的,該函數被稱爲 散列函數
順序索引

順序索引中,根據包含記錄是否按照搜索碼制定的順序排序能夠分爲彙集索引和非彙集索引mysql

  • 被索引文件中的記錄自身也能夠按照某種排序順序存儲,一個文件能夠有多個索引,分別預計不一樣的搜索碼,若是包含記錄的文件按照某個搜索碼指定的順序排序,那麼改搜索碼對應的索引稱爲彙集索引,也稱爲主索引(Primary Index)。
  • 搜索碼指定的順序於文件中記錄的物理順序不一樣的索引被稱爲非彙集索引或者輔助索引
  • 非彙集索引和彙集索引的區別在於,經過彙集索引能夠查到須要查找的數據,而經過非彙集索引能夠查到記錄對應的主鍵值, 再使用主鍵的值經過彙集索引查找到須要的數據
  • 關於數據庫中 Key,Primary Key,Unique Key 與 index 的不一樣能夠參見這篇文章
  • 關於順序索引,通常若是沒有明確表示的話指的就是 B+Tree 索引
哈希索引
  • 哈希索引(hash index)基於哈希表實現,只有精確匹配索引全部咧的查詢纔有效;對於每一行數據,存儲引擎都會對全部的索引計算一個哈希碼,哈希嗎是一個較小的值,而且不一樣鍵值計算出來的哈希碼也不同。哈希索引將全部的哈希碼存儲在索引中,同時在哈希表中保存指向每一個數據行的指針。
聚簇索引和非聚簇索引並非單獨的索引類型,而是一種數據存儲方式 [高性能 MySQL]

索引的優勢和缺點

優勢
  • 索引大大減小減小了服務器須要掃描的數據量
  • 索引能夠幫助服務器避免排序和臨時表
  • 索引能夠將隨機 I/O 變爲順序 I/O
缺點
索引的缺點主要是針對不合理索引而言的,對於開發者而言,索引維護所耗費的資源和索引所提供的快速查詢能力節省的時間資源二者進行取捨。
  • 建立和維護索引都須要耗費時間,並且隨着數據量的增長而增長。
  • 每一個索引還須要耗費額外的物理空間資源。
  • 當對錶中的數據進行 CURD 操做的時候,索引也須要對應的動態維護,增長數據維護成本。

索引的數據結構

MySQL 存儲索引的時候通常咱們沒有明確之處其他結構就是指的是 B-Tree 數據結構存儲索引。 參考資料

衛星數據:指的是索引元素所指向的數據記錄,好比數據庫的某一行。算法

磁盤 I/O

  • 動態查詢樹主要有:二叉查找樹,平衡二叉查找樹,紅黑樹,B-Tree/B+Tree/B*Tree
  • 前三者是典型的二叉查找樹結構,其查找的時間複雜度O(log2N)與樹的深度相關,那麼下降樹的深度天然對查找效率是有所提升的;還有一個實際問題:就是大規模數據存儲中,實現索引查詢這樣一個實際背景下,樹節點存儲的元素數量是有限的(若是元素數量很是多的話,查找就退化成節點內部的線性查找了),這樣致使二叉查找樹結構因爲樹的深度過大而形成磁盤I/O讀寫過於頻繁,進而致使查詢效率低下,那麼如何減小樹的深度,一個基本的想法就是:採用多叉樹結構(因爲樹節點元素數量是有限的,天然該節點的子樹數量也就是有限的)。
  • 關於硬盤(外存儲器)的解讀,磁盤獲取數據由三部分時間組成:查找時間(代價最高),等待時間,傳輸時間.
  • 磁盤讀取數據是以盤塊(block)爲基本單位的。位於同一盤塊中的全部數據都能被一次性所有讀取出來。而磁盤IO代價主要花費在查找時間Ts上。所以咱們應該儘可能將相關信息存放在同一盤塊,同一磁道中。或者至少放在同一柱面或相鄰柱面上,以求在讀/寫信息時儘可能減小磁頭來回移動的次數,避免過多的查找時間Ts。
  • 當咱們利用索引查詢的時候,顯然不可能把整個索引所有加載到內存中,只能逐一加載每個磁盤頁,這裏的磁盤頁對應這索引樹的節點.
  • 在使用樹結構查詢的時候若是使用二叉樹,那麼磁盤的 IO 次數最壞狀況下等於索引樹的高度.此時索引樹的高度就對查詢資源相當重要.

B-Tree Wiki

什麼是 B-Tree?一個 m 階的 B 樹有如下幾個特徵。sql

  1. 根節點至少有兩個子節點
  2. 每一箇中間節點都包含 K-1個元素和 K 個子節點,其中 m/2<=k<=m
  3. 每個葉子節點都包含 K-1個元素,其中 m/2<=k<=m
  4. 全部的葉子節點都位於同一層
  5. 每一個非終端節點中包含有 n 個關鍵詞信息(n,P0,K1,P1,K2....Kn,Pn)
    a. 其中 n 的取值範圍是ceil(m/2)-1<=n<=m-1
    b. Ki爲關鍵字,且關鍵字按照順序排序(K(i-1)<Ki)
    c. Pi爲指向子樹根的接點,且指針P(i-1)指向子樹種全部結點的關鍵字均小於Ki,但都大於K(i-1)。[每一個節點中的元素從小到大排列,節點當中 K-1個元素正好是 K 個孩子包含元素的值域劃分]

clipboard.png

模擬查找文件29的過程:

(1) 根據根結點指針找到文件目錄的根磁盤塊1,將其中的信息導入內存。【磁盤IO操做1次】數據庫

(2) 此時內存中有兩個文件名17,35和三個存儲其餘磁盤頁面地址的數據。根據算法咱們發現17<29<35,所以咱們找到指針p2。服務器

(3) 根據p2指針,咱們定位到磁盤塊3,並將其中的信息導入內存。【磁盤IO操做2次】數據結構

(4) 此時內存中有兩個文件名26,30和三個存儲其餘磁盤頁面地址的數據。根據算法咱們發現26<29<30,所以咱們找到指針p2。函數

(5) 根據p2指針,咱們定位到磁盤塊8,並將其中的信息導入內存。【磁盤IO操做3次】性能

(6) 此時內存中有兩個文件名28,29。根據算法咱們查找到文件29,並定位了該文件內存的磁盤地址。大數據

分析上面的過程,發現須要3次磁盤IO操做和3次內存查找操做。關於內存中的文件名查找,因爲是一個有序表結構,能夠利用折半查找提升效率。至於3次磁盤IO操做時影響整個B-tree查找效率的決定因素。

固然,若是咱們使用平衡二叉樹的磁盤存儲結構來進行查找,磁盤IO操做最少4次,最多5次。並且文件越多,B-tree比平衡二叉樹所用的磁盤IO操做次數將越少,效率也越高。

clipboard.png
B 樹中每一個節點都具備衛星數據

節點的插入
插入(insert)操做:插入一個元素時,首先在B-tree中是否存在,若是不存在,即在葉子結點處結束,而後在葉子結點中插入該新的元素,注意:若是葉子結點空間足夠,這裏須要向右移動該葉子結點中大於新插入關鍵字的元素,若是空間滿了以至沒有足夠的空間去添加新的元素,則將該結點進行「分裂」,將一半數量的關鍵字元素分裂到新的其相鄰右結點中,中間關鍵字元素上移到父結點中(固然,若是父結點空間滿了,也一樣須要「分裂」操做),並且當結點中關鍵元素向右移動了,相關的指針也須要向右移。若是在根結點插入新元素,空間滿了,則進行分裂操做,這樣原來的根結點中的中間關鍵字元素向上移動到新的根結點中,所以致使樹的高度增長一層。

例:自頂向下查找4的節點位置,發現4應當插入到節點元素3,5之間

clipboard.png

節點3,5已是兩元素節點,沒法再增長。父親節點 2, 6 也是兩元素節點,也沒法再增長。根節點9是單元素節點,能夠升級爲兩元素節點。因而拆分節點3,5與節點2,6,讓根節點9升級爲兩元素節點4,9。節點6獨立爲根節點的第二個孩子

clipboard.png

節點的刪除操做
刪除(delete)操做:首先查找B-tree中需刪除的元素,若是該元素在B-tree中存在,則將該元素在其結點中進行刪除,若是刪除該元素後,首先判斷該元素是否有左右孩子結點,若是有,則上移孩子結點中的某相近元素到父節點中,而後是移動以後的狀況;若是沒有,直接刪除後,移動以後的狀況.。

刪除元素,移動相應元素以後,若是某結點中元素數目小於ceil(m/2)-1,則須要看其某相鄰兄弟結點是否豐滿(結點中元素個數大於ceil(m/2)-1),若是豐滿,則向父節點借一個元素來知足條件;若是其相鄰兄弟都剛脫貧,即借了以後其結點數目小於ceil(m/2)-1,則該結點與其相鄰的某一兄弟結點進行「合併」成一個結點,以此來知足條件。那我們經過下面實例來詳細瞭解吧。

例:刪除11節點

clipboard.png

刪除11後,節點12只有一個孩子,不符合B樹規範。所以找出12,13,15三個節點的中位數13,取代節點12,而節點12自身下移成爲第一個孩子。(這個過程稱爲左旋)

clipboard.png

B+Tree

B+Tree 是對於 B-Tree 的一種變體,有着比 B-Tree 更高的查詢效率。

一個 m 階的 B 樹有着以下特色

  1. 有 K 個子樹的中間節點包含有k個元素(B樹中是k-1個元素),每一個元素不保存數據,只用來索引,全部數據都保存在葉子節點。
  2. 全部的葉子結點中包含了所有元素的信息,及指向含這些元素記錄的指針,且葉子結點自己依關鍵字的大小自小而大順序連接。
  3. 全部的中間節點元素都同時存在於子節點,在子節點元素中是最大(或最小)元素。

clipboard.png

根節點的最大元素等榮譽整個樹的最大元素,葉子節點包含了全量元素信息,而且每個葉子節點都帶有指向下一個節點的指針,造成了一個有序鏈表.

clipboard.png

在 B+Tree 樹中,只有葉子節點保存衛星數據.

clipboard.png

須要補充的是,在數據庫的彙集索引(Clustered Index)中,葉子節點直接包含衛星數據。在非彙集索引(NonClustered Index)中,葉子節點帶有指向衛星數據的指針。

因爲 B+Tree 樹的中間節點沒有衛星數據,因此一樣大小的磁盤也(1~4K)能夠容納更多的元素,意味着查詢的 IO 次數越少。

另外,B+Tree 的查詢必須最終查詢到葉子節點,和 B-Tree 不一樣,查詢性能最穩定,並且在範圍查詢中,比起只能以來繁瑣的中序遍歷的 B 樹,更有效率。

綜合來看,B+Tree 對比 B-Tree 有三大優點:

  1. 單一節點存儲更多的元素,使得查詢的IO次數更少。
  2. 全部查詢都要查找到葉子節點,查詢性能穩定。
  3. 全部葉子節點造成有序鏈表,便於範圍查詢。

哈希索引

關於哈希索引,目前 MySQL 中只有 Memory 和 NDB 兩種引擎支持,詳細瞭解能夠參考這篇文章 MySQL索引之哈希索引,本文不在贅述。

索引的使用

理解了索引的數據結構以後咱們就理解了索引在建立和使用上的一些方法(如下所描述的索引均指的是 B-Tree 索引)。
create table People (
    last_name   varchar(50)     not null,
    first_name  varchar(50)     not null,
    dob         date            not null,
    gendar      enum('m','f')   not null,
    key(last_name, first_name, dob)
);
  • 使用 B-Tree 索引的查詢類型適用於下列集中狀況
  1. 全值匹配

    select * from People where last_name = 'Allen' and first_name = 'Cuba' and dob = '1999-01-01';

  2. 匹配最左前綴

    select * from People where last_name = 'Allen';

  3. 匹配列前綴

    select * from People where last_name like 'A%';

  4. 匹配範圍值

    select * from People where last_name > 'Allen' and last_name < 'Bob';

  5. 精確匹配某一列並範圍匹配另一列

    select * from People where last_name = 'Allen' and last_name like 'B%';

  6. 只訪問索引的查詢

    select last_name,

    first_name, 
        dob

    from People
    where last_name = 'Allen'

    and first_name = 'Cuba' 
    and dob = '1999-01-01';
  • 關於使用索引的一些限制
  1. 若是不是按照索引的最左列開始查找,則沒法使用索引

    select * from People where first_name = 'Allen';

  2. 不能跳過索引中的列

    select * from People where last_name = 'Allen' and dob = '1999-01-01';

  3. 若是查詢中有某個列的範圍查詢,則其右邊的全部咧都沒法使用索引優化查詢

    select * from People where last_name = 'Allen' and first_name like 'J%' and dob = '1999-01-01';

高性能的索引策略

參考資料

  1. 最左前綴匹配原則,很是重要的原則,mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就中止匹配,好比a = 1 and b = 2 and c > 3 and d = 4 若是創建(a,b,c,d)順序的索引,d是用不到索引的,若是創建(a,b,d,c)的索引則均可以用到,a,b,d的順序能夠任意調整。
  2. 使用獨立的列
  3. 多列索引:MySQL 從5.0以後的更新版本引入了一種叫索引合併策略,關於這項策略能夠參考MySQL 優化之 index merge
  4. =和in能夠亂序,好比a = 1 and b = 2 and c = 3 創建(a,b,c)索引能夠任意順序,mysql的查詢優化器會幫你優化成索引能夠識別的形式
  5. 儘可能選擇區分度高的列做爲索引,區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大咱們掃描的記錄數越少,惟一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不一樣,這個值也很難肯定,通常須要join的字段咱們都要求是0.1以上,即平均1條掃描10條記錄
  6. 索引列不能參與計算,保持列「乾淨」,好比from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,緣由很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,須要把全部元素都應用函數才能比較,顯然成本太大。因此語句應該寫成create_time = unix_timestamp(’2014-05-29’);
  7. 儘可能的擴展索引,不要新建索引。好比表中已經有a的索引,如今要加(a,b)的索引,那麼只須要修改原來的索引便可

一些有價值的參考資料

相關文章
相關標籤/搜索