理解索引(上)

情情說發表於2018-05-26

最近有個需求,要修改現有儲存結構,涉及查詢條件和查詢效率的考量,看了幾篇索引和HBase相關的文章,回憶了相關知識,結合專案需求,說說自己的理解和總結。

部分內容摘錄了幾個博友的文章,最後會給出文章連結,感謝他們的精彩分析。

會從以下幾個方面介紹:

  • 為什麼需要索引
  • 索引的類別
  • MySQL索引演化
  • MySQL索引優化
  • HBase介紹
  • HBase儲存結構
  • HBase索引介紹
  • 業務需求及設計

準備分3篇文章介紹,這篇主要介紹前3小節,理解我們常常說的MySQL索引,第2篇重點介紹索引分析方法和常見索引優化,第3篇介紹下業務中使用的HBase。

為什麼需要索引

總體來說,索引是為了提高查詢速度的,當資料量比較大時,從頭到尾依次檢索是無法接受的。另外,儲存的資料會包含多個屬性來描述業務實體,屬性可以連續或分開儲存,分別對應到MySQL和HBase。

以MySQL為例,學生這個實體有學號、姓名、性別、所屬班級等屬性,一般還有個主鍵ID,現在有個需求:查詢學號為002的學生姓名。

學生實體

資料是儲存在磁碟上的,作業系統讀取磁碟的最小單位是塊,如果沒有索引,會載入所有的資料到記憶體,依次進行檢索,載入的總資料會很多,磁碟IO多。

如果有了索引,會以學號為key建立索引,MySQL採用B+樹結構儲存,一方面載入的資料只有學號和主鍵ID,另一方便採用了多叉平衡樹,定位到指定學號會很快,根據關聯的ID可以快速定位到對應行的資料,所以檢索的速度會很快,因為載入的總資料很少,磁碟IO少。

可見,索引可以大大減少檢索資料的範圍、減少磁碟IO,使查詢速度很快,因為磁碟IO是很慢的,是由它的硬體結構決定的。

下面,再詳細介紹下磁碟儲存結構和資料定位過程,加深對索引的理解。

硬碟儲存結構

磁碟內部結構如下:

理解索引(上)

  • 很多個碟片被串在一個主軸上,主軸帶著他們快速的旋轉;
  • 每個碟片都有很多一圈一圈的磁軌,每個磁軌又分為一個一個的扇區;
  • 多個碟片上的同一位置的磁軌組成了一個柱面;
  • 每個碟片上都有可以讀寫資料的磁頭;

訪問資料時,可以說:把1柱面,1磁頭,1扇區的資料取出來?

磁碟會把1磁頭挪到1柱面,就是對應的磁軌, 這叫“尋道時間”,然後再旋轉磁碟,讓磁頭指向1扇區,開始讀取資料,這叫“旋轉時間”,轉速快的硬碟能更快的旋轉到特定扇區,所以效能會更好。

結構比較複雜,但作業系統給封裝了,提供了一個叫做邏輯塊的方式,你看到磁碟就是有一個個“塊”組成的,編號為1, 2, 3, ..n,把指定的塊轉化成柱面,磁頭,扇區, 按照上面說的方法尋道,旋轉,讀取資料。

為了方便我們操作,作業系統又進一步抽象,抽象成了檔案,由檔案系統去儲存和讀取資料,我們只需要與檔案打交道,檔案使用inode結構,通過它可以輕鬆的找到這個檔案所使用的所有磁碟塊。

inode

扇區是對硬碟而言,塊是對檔案系統而言,塊是檔案存取的最小單位,一般一個塊由連續的幾個扇區組成。

一個表的資料塊以連結串列的方式串聯在一起,資料以行為單位一行一行的存放在磁碟的塊中。

表在磁碟的儲存

資料定位過程

以MySQL的B+樹為例,簡單說下幾種常見場景下,資料的定位過程。

第一種場景:索引精確查詢

select * from user_info where id = 23 ;
複製程式碼

確定定位條件, 找到根節點Page No, 根節點讀到記憶體, 逐層向下查詢, 讀取葉子節點Page,通過 二分查詢找到記錄或未命中。

索引精確查詢

第二種場景:索引範圍查詢

select * from user_info where id >= 18 and id < 22 ;
複製程式碼

讀取根節點至記憶體, 確定索引定位條件id=18, 找到滿足條件第一個葉節點, 順序掃描所有結果, 直到終止條件滿足id >=22。

索引範圍查詢

第三種場景:全表掃描

select * from user_info where name = 'abc' ;
複製程式碼

直接讀取葉節點頭結點, 順序掃描, 返回符合條件記錄, 到最終節點結束

全表掃描

第四中場景:二級索引查詢

create table table_x(int id primary key, varchar(64) name , key sec_index(name) ) ;
select * from table_x where name = 'd' ;
複製程式碼

通過二級索引查出對應主鍵,拿主鍵回表查主鍵索引得到資料, 二級索引可篩選掉大量無效記錄,提高效率

二級索引查詢

索引的類別

上面介紹了索引的優點和資料的定位過程,對其有了整體瞭解,另外,索引有不同種類和不同的實現方式,這節重點梳理下這些概念。

聚簇索引與非聚簇索引

簡單來說,聚集索引是一種索引組織形式,索引的鍵值邏輯順序決定了表資料行的物理儲存順序,而非聚集索引則就是普通索引了,僅僅只是對資料列建立相應的索引,不影響整個表的物理儲存順序。

聚簇索引的優點有:

  • 範圍查詢效率更高;
  • 特別適合有一小部分熱點資料頻繁讀寫的場景;
  • 通過主鍵訪問資料時快速可達;

InnoDB引擎是聚集索引組織表,它的聚集索引選擇規則是這樣的:

  • 首先選擇顯式定義的主鍵索引做為聚集索引;
  • 如果沒有,則選擇第一個不允許NULL的唯一索引;
  • 還是沒有的話,就採用InnoDB引擎內建的ROWID作為聚集索引;
主鍵索引和輔助索引

主鍵索引,簡稱主鍵,由一個或多個列組成,用於唯一性標識資料表中的某一條記錄。

InnoDB資料表的主鍵設計通常遵循幾個原則:

  • 採用一個沒有業務用途的自增屬性列作為主鍵;
  • 主鍵欄位值總是不更新,只有新增或者刪除兩種操作;
  • 不選擇會動態更新的型別,比如當前時間戳等;

輔助索引,常規所指的索引,也叫二級索引,又分為唯一索引和非唯一索引。

InnoDB引擎中,主鍵索引會被選中作為聚集索引,而唯一索引和普通輔助索引間除了唯一性約束外,在儲存上沒本質區別。

MySQL索引演化

本小節主要介紹索引是如何根據需求一步步演變最終成為B+樹結構,基本思路是減少訪問資料的總量,相應的減少磁碟IO。

密集索引(Dense Index )

根據減少無效資料訪問的原則,我們將鍵的值拿過來存放到獨立的塊中。並且為每一個鍵值新增一個指標, 指向原來的資料塊。

密集索引

當進行定位操作時,不再進行表掃描。而是進行索引掃描(Index Scan),依次讀出所有的索引塊,進行鍵值的匹配,這樣帶來的問題是需要很多空間來儲存Dense索引。另外索引的定位效率也比較低,可以通過排序和查詢演算法來減少IO的訪問。

假設一個塊中能儲存100行資料,10,000,000行的資料需要100,000個塊的儲存空間。假設鍵值列(+指標)佔用一行資料1/10的空間。那麼大約需要10,000個塊來儲存Dense索引。因此我們用大約1/10的額外儲存空間換來了大約全表掃描10倍的定位效率。

折半塊查詢

需要對Dense進行索引,每個索引塊內是有序的,另外,需要一個陣列按順序儲存索引塊地址,這樣整體就有序了,陣列也要儲存到磁碟上,放在單獨的塊鏈中。

折半查詢的時間複雜度是O(log2(N)),在上面的列子中,dense索引總共有10,000個塊。假設1個塊能儲存2000個指標,需要5個塊來儲存這個陣列。通過折半塊查詢,我們最多隻需要讀取5(陣列塊)+ 14(索引塊log 2(10000))+1(資料塊)=20個塊。

折半塊查詢

從圖中可以看到,相對於密集索引,編號是有序的。

稀疏索引(Sparse Index)

介紹基於塊的折半查詢時發現,讀出每個塊後只需要和第一行的鍵值匹配,就可以決定下一個塊的位置(方向)。 因此有效資料是每個塊的第一行資料,將每一個塊的第一行的資料單獨拿出來,和索引陣列的地址放到一起。這樣就可以直接在這個陣列上進行折半查詢了,這個陣列就進化成了Sparse Index了。

稀疏索引

需要10個塊來儲存10000個Dense Index塊的地址和首行鍵值,通過Sparse索引,僅需要讀取10(Sparse塊)+1(Dense塊)+1(資料塊)=12個塊。

多層稀疏索引

因為Sparse Index本身是有序的,所以可以為Sparse Index再建sparse Index。通過這個方法,一層一層的建立 Sparse Indexes,直到最上層的Sparse Index只佔用一個塊為止。

多層稀疏索引

這個例子中,Sparse Index只有10個塊,只需要再建立一個Sparse Index.通過兩層Sparse Index和一層Dense Index查詢時,只需讀取1+1+1+1=4個塊。

如果資料本身是基於某個Key來排序的,那麼可以直接在資料上建立sparse索引,而不需要建立一個dense索引層(可以認為資料就是dense索引層),這就是說的聚集索引。

B+樹

由於鍵值是有序的,因此可以進行範圍查詢。只需要將資料塊、Dense Index塊分別以雙向連結串列的方式進行連線, 就可以實現高效的範圍查詢。如下圖所示:

範圍索引

這就是我們常說的B+ Tree,倒過來看下:

B+樹

最後總結下它的特點:

  • 每次進行定位操作時,都從根開始查詢;
  • 每層索引只需要讀出一個塊;
  • 最底層的Dense Index或資料稱作葉子(leaf);
  • 每次查詢都必須要搜尋到葉子節點,才能定位到資料;
  • 索引的層數稱作索引樹的高度(height);
  • 索引的IO效能和索引樹的高度密切相關,索引樹越高,磁碟IO越多;
  • 進行範圍查詢時,效率很高;

參考文章:

  1. MYSQL-B+TREE索引原理
  2. 我是一塊硬碟
  3. FAQ系列 | MySQL索引之聚集索引
  4. 由淺入深理解索引的實現

歡迎掃描下方二維碼,關注我的個人微信公眾號,檢視更多文章 ~

情情說

相關文章