轉自:https://blog.csdn.net/every__day/article/details/90763607算法
《數據結構與算法之美》數據庫
前面講過MySQL數據庫索引實現原理,底層是依賴B+樹這種數據結構來實現的。那相似Redisp 這要的Key-Value數據庫中的索引,又是怎麼實現的呢?底層依賴的又是什麼數據結構呢?數組
爲何須要索引?
在實際的軟件開發中,業務紛繁複雜,功能變幻無窮,可是,萬變不離其宗。若是拋開業務和功能的外殼,其實它們的本質均可以抽象爲「對數據的存儲和計算」。對應到數據結構和算法中,那「存儲」須要的就是數據結構,「計算」須要的就是算法。數據結構
對於存儲的需求,功能上無外乎增刪改查。這其實並不複雜。可是,一旦存儲的數據多了,那性能就成了這些系統要關注的重點,特別是在一些跟存儲相關的基礎系統(好比MySQL數據庫、分佈式文件系統等)、中間件(好比消息中間件RocketMQ等)中。數據結構和算法
「如何節省存儲空間、如何提升數據增刪改查的執行效率」,這個問題就成了設計的重點。而這些系統的實現,都離不開一個東西,那就是索引。不誇張的說,索引設計的好壞,直接決定了這些系統是否優秀。分佈式
索引這個概念,很是好理解。你能夠類比書籍的目錄來理解。若是沒有目錄,咱們想要查找某個知識點的時候,就要一頁一頁的翻。經過目錄,咱們就能夠快速定位相關的知識點的頁數,查找的速度也會有質的提升。性能
索引的需求定義
索引的概念不難理解,在設計索引的過程當中,須要考慮一些因素,換名話說,咱們該如何定義清楚需求呢?搜索引擎
對於系統設計需求,咱們通常能夠從功能性需求和非功能性需求兩方面來分析。.net
1. 功能性需求設計
對於功能性需求大體要考慮如下幾點。
數據是格式化的仍是非格式化數據?要構建索引的原始數據,類型不少。我把它分爲兩類,一類是結構化數據,好比MySQL中的數據;另外一類是非結構化數據,好比搜索引擎中的網頁。對於非結構化數據,咱們通常須要作預處理,提取出查詢關鍵詞,對關鍵詞構建索引。
數據是靜態數據仍是動態數據?若是原始是一組靜態數據,也就是說,不會有數據的增長、刪除、更新操做,因此,咱們在構建索引的時候,只須要考慮查詢效率就能夠了。這樣,索引的構建就相對簡單些。不過,大部分狀況下,咱們都是對動態數據構建索引,也就是說,咱們不只要考慮到索引的查詢效率,在原始數據更新時,咱們還須要動態的更新索引。支持動態數據集合的索引,設計越來相對更復雜些。
索引是存儲在內存仍是硬盤?若是索引存儲在內存中,那技術要求的速度確定要比存儲的磁盤中的高。可是,若是原始數據量很大的狀況下,對應的索引可能也會很大。這個時候,由於內存有限,咱們可能就不得不將索引存儲在硬盤中了。實際上,還有第三種狀況,那就是一部分存儲在內存,一部分存儲在磁盤,這樣就能夠兼顧內存消耗和查詢效率。
單值查找仍是區間查找?所謂單值查找,也就是根據查詢關鍵詞等於某個值的數據。這種查詢需求最多見。所謂區間查找,就是查找關鍵詞處於某個區間值的全部數據。實際上,不一樣的應用場景,查詢的需求會多種多樣。
單關鍵詞查找仍是多關鍵詞組合查找?好比,搜索引擎中構建的索引,既要支持一個關鍵詞的查找,好比「數據結構」,也要支持組合關鍵詞查找,好比「數據結構 AND算法」。對於單關鍵詞查找,索引構建起來相對簡單些。對於多關鍵詞查找來講,要分多種狀況。像MySQL這種結構化數據的查詢需求,咱們能夠實現針對多個關鍵詞組合,創建索引;對於像搜索引擎這樣的非結構數據的查詢需求,咱們能夠針對間個關鍵詞構建索引,而後經過集合操做,好比求並集、求交集等,計算出多個關鍵詞組合的查詢結果。
實際上,不一樣的場景,不一樣的原始數據,對於索引的需求也會千差萬別。
2.非功能性需求
不論是存儲在內存中仍是磁盤中,索引對存儲空間的消耗不能過大。若是存儲在內存中,索引對佔用存儲空間的限制就會很是苛刻。畢竟內存空間很是有限,一箇中間件啓動後就佔用幾個GB的內存,開發者顯然是沒法接受的。若是存儲在硬盤中,那索引對佔用存儲空間的限制,稍微會放寬一些。可是,咱們也不能掉以輕心。由於,有時候,索引對存儲空間消耗會超過數據。
在考慮索引查詢效率的同時,咱們仍是考慮索引的維護成本。索引的目的是提升查詢效率,可是,基於動態數據集合構建的索引,咱們還要考慮到索引的維護成本。由於在原始數據動態增刪改的同時,咱們也須要動態的更新索引。而索引的更新勢必會影響到增刪改的操做性能。
構建索引經常使用的數據結構有哪些?
實際上,經常使用來構建索引的數據結構,就是咱們以前講過的幾種支持動態數據集合的數據結構。好比,散列表、紅黑樹、跳錶、B+樹。除此以外,位圖、布隆過濾器能夠做爲輔助索引,有序數組能夠用來對靜態數據構建索引。
咱們知道,散列表增刪改查操做的性能很是好,時間複雜度是O(1)。一些鍵值數據庫,好比Redis、Memcache,就是使用散列表來構建索引的。這類索引,通常都構建在內存中。
紅黑樹做爲一種經常使用的平衡二叉查找樹,數據插入、刪除、查找的時間複雜度是O(logn),也很是適合用來構建內存索引。Ext文件系統中,對磁盤塊的索引,用的就是紅黑樹。
B+ 樹比起紅黑樹來講,更加適合構建存儲在磁盤的索引。B+樹是一個多叉樹,因此,以相同個數的數據構建索引,B+樹的高度要低於紅黑樹。當藉助索引查詢數據的時候,讀取B+樹索引,須要的磁盤IO次數更少。因此,大部分關係型數據庫的索引,好比MySQL、Oracle,都是用B+樹來實現的。
跳錶也支持快速添加、刪除、查找數據。並且,咱們經過靈活調整索引結點個數和數據個數之間的比例,能夠很好的平衡對內存的消耗及其查詢效率。Redis中的有序集合,就是用跳錶來構建的。
除了散列表、紅黑樹、B+樹、跳錶以外,位圖和布隆過濾器這兩個數據結構,也能夠用於索引中,輔助存儲在磁盤中的索引,加速數據查詢的效率。咱們來看下,具體是怎麼作的?
布隆過濾器有必定的判錯率。可是,咱們能夠規避它的短處,發揮它的長處。儘管對於斷定存在的數據,有可能並不存在,可是對於斷定不存在的數據,那確定就不存在。並且,布隆過濾器還有一個更大的特色,那就是內存佔用很是少。咱們能夠針對數據,構建一個布隆過濾器,而且存儲在內存中。當要查詢數據的時候,咱們能夠先經過布隆過濾器,斷定是否存在。若是數據不存在,那咱們就不必讀取磁盤中的索引了。對於數據不存在的狀況,數據查詢就更加快速了。
實際上,有序數組也能夠被做爲索引。若是數據是靜態的,也就是不會插入、刪除、更新操做,那咱們能夠把數據的關鍵詞(查詢用的)抽取出來,組織成有序數組,而後利用二分查找算法來快速查找數據。