最近有個需求,要修改現有存儲結構,涉及查詢條件和查詢效率的考量,看了幾篇索引和HBase相關的文章,回憶了相關知識,結合項目需求,說說本身的理解和總結。html
部份內容摘錄了幾個博友的文章,最後會給出文章連接,感謝他們的精彩分析。node
會從如下幾個方面介紹:mysql
準備分3篇文章介紹,這篇主要介紹前3小節,理解咱們經常說的MySQL索引,第2篇重點介紹索引分析方法和常見索引優化,第3篇介紹下業務中使用的HBase。算法
整體來講,索引是爲了提升查詢速度的,當數據量比較大時,從頭至尾依次檢索是沒法接受的。另外,存儲的數據會包含多個屬性來描述業務實體,屬性能夠連續或分開存儲,分別對應到MySQL和HBase。sql
以MySQL爲例,學生這個實體有學號、姓名、性別、所屬班級等屬性,通常還有個主鍵ID,如今有個需求:查詢學號爲002的學生姓名。數組
數據是存儲在磁盤上的,操做系統讀取磁盤的最小單位是塊,若是沒有索引,會加載全部的數據到內存,依次進行檢索,加載的總數據會不少,磁盤IO多。微信
若是有了索引,會以學號爲key建立索引,MySQL採用B+樹結構存儲,一方面加載的數據只有學號和主鍵ID,另外一方便採用了多叉平衡樹,定位到指定學號會很快,根據關聯的ID能夠快速定位到對應行的數據,因此檢索的速度會很快,由於加載的總數據不多,磁盤IO少。性能
可見,索引能夠大大減小檢索數據的範圍、減小磁盤IO,使查詢速度很快,由於磁盤IO是很慢的,是由它的硬件結構決定的。優化
下面,再詳細介紹下磁盤存儲結構和數據定位過程,加深對索引的理解。ui
磁盤內部結構以下:
訪問數據時,能夠說:把1柱面,1磁頭,1扇區的數據取出來?
磁盤會把1磁頭挪到1柱面,就是對應的磁道, 這叫「尋道時間」,而後再旋轉磁盤,讓磁頭指向1扇區,開始讀取數據,這叫「旋轉時間」,轉速快的硬盤能更快的旋轉到特定扇區,因此性能會更好。
結構比較複雜,但操做系統給封裝了,提供了一個叫作邏輯塊的方式,你看到磁盤就是有一個個「塊」組成的,編號爲1, 2, 3, ..n,把指定的塊轉化成柱面,磁頭,扇區, 按照上面說的方法尋道,旋轉,讀取數據。
爲了方便咱們操做,操做系統又進一步抽象,抽象成了文件,由文件系統去保存和讀取數據,咱們只須要與文件打交道,文件使用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引擎是彙集索引組織表,它的彙集索引選擇規則是這樣的:
主鍵索引,簡稱主鍵,由一個或多個列組成,用於惟一性標識數據表中的某一條記錄。
InnoDB數據表的主鍵設計一般遵循幾個原則:
輔助索引,常規所指的索引,也叫二級索引,又分爲惟一索引和非惟一索引。
InnoDB引擎中,主鍵索引會被選中做爲彙集索引,而惟一索引和普通輔助索引間除了惟一性約束外,在存儲上沒本質區別。
本小節主要介紹索引是如何根據需求一步步演變最終成爲B+樹結構,基本思路是減小訪問數據的總量,相應的減小磁盤IO。
根據減小無效數據訪問的原則,咱們將鍵的值拿過來存放到獨立的塊中。而且爲每個鍵值添加一個指針, 指向原來的數據塊。
當進行定位操做時,再也不進行表掃描。而是進行索引掃描(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了。
須要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索引層),這就是說的彙集索引。
因爲鍵值是有序的,所以能夠進行範圍查找。只須要將數據塊、Dense Index塊分別以雙向鏈表的方式進行鏈接, 就能夠實現高效的範圍查找。以下圖所示:
這就是咱們常說的B+ Tree,倒過來看下:
最後總結下它的特色:
參考文章:
歡迎掃描下方二維碼,關注個人我的微信公衆號,查看更多文章 ~