索引:如何在海量數據中快速查找某個數據?


轉自:https://blog.csdn.net/every__day/article/details/90763607

《數據結構與算法之美》

前面講過MySQL數據庫索引實現原理,底層是依賴B+樹這種數據結構來實現的。那類似Redisp 這要的Key-Value數據庫中的索引,又是怎么實現的呢?底層依賴的又是什么數據結構呢?

為什么需要索引?
在實際的軟件開發中,業務紛繁復雜,功能千變萬化,但是,萬變不離其宗。如果拋開業務和功能的外殼,其實它們的本質都可以抽象為“對數據的存儲和計算”。對應到數據結構和算法中,那“存儲”需要的就是數據結構,“計算”需要的就是算法。

對於存儲的需求,功能上無外乎增刪改查。這其實並不復雜。但是,一旦存儲的數據多了,那性能就成了這些系統要關注的重點,特別是在一些跟存儲相關的基礎系統(比如MySQL數據庫、分布式文件系統等)、中間件(比如消息中間件RocketMQ等)中。

“如何節省存儲空間、如何提高數據增刪改查的執行效率”,這個問題就成了設計的重點。而這些系統的實現,都離不開一個東西,那就是索引。不誇張的說,索引設計的好壞,直接決定了這些系統是否優秀。

索引這個概念,非常好理解。你可以類比書籍的目錄來理解。如果沒有目錄,我們想要查找某個知識點的時候,就要一頁一頁的翻。通過目錄,我們就可以快速定位相關的知識點的頁數,查找的速度也會有質的提高。

索引的需求定義
索引的概念不難理解,在設計索引的過程中,需要考慮一些因素,換名話說,我們該如何定義清楚需求呢?

對於系統設計需求,我們一般可以從功能性需求和非功能性需求兩方面來分析。

1. 功能性需求

對於功能性需求大致要考慮以下幾點。

數據是格式化的還是非格式化數據?要構建索引的原始數據,類型很多。我把它分為兩類,一類是結構化數據,比如MySQL中的數據;另一類是非結構化數據,比如搜索引擎中的網頁。對於非結構化數據,我們一般需要做預處理,提取出查詢關鍵詞,對關鍵詞構建索引。

數據是靜態數據還是動態數據?如果原始是一組靜態數據,也就是說,不會有數據的增加、刪除、更新操作,所以,我們在構建索引的時候,只需要考慮查詢效率就可以了。這樣,索引的構建就相對簡單些。不過,大部分情況下,我們都是對動態數據構建索引,也就是說,我們不僅要考慮到索引的查詢效率,在原始數據更新時,我們還需要動態的更新索引。支持動態數據集合的索引,設計越來相對更復雜些。

索引是存儲在內存還是硬盤?如果索引存儲在內存中,那技術要求的速度肯定要比存儲的磁盤中的高。但是,如果原始數據量很大的情況下,對應的索引可能也會很大。這個時候,因為內存有限,我們可能就不得不將索引存儲在硬盤中了。實際上,還有第三種情況,那就是一部分存儲在內存,一部分存儲在磁盤,這樣就可以兼顧內存消耗和查詢效率。

單值查找還是區間查找?所謂單值查找,也就是根據查詢關鍵詞等於某個值的數據。這種查詢需求最常見。所謂區間查找,就是查找關鍵詞處於某個區間值的所有數據。實際上,不同的應用場景,查詢的需求會多種多樣。

單關鍵詞查找還是多關鍵詞組合查找?比如,搜索引擎中構建的索引,既要支持一個關鍵詞的查找,比如“數據結構”,也要支持組合關鍵詞查找,比如“數據結構 AND算法”。對於單關鍵詞查找,索引構建起來相對簡單些。對於多關鍵詞查找來說,要分多種情況。像MySQL這種結構化數據的查詢需求,我們可以實現針對多個關鍵詞組合,建立索引;對於像搜索引擎這樣的非結構數據的查詢需求,我們可以針對間個關鍵詞構建索引,然后通過集合操作,比如求並集、求交集等,計算出多個關鍵詞組合的查詢結果。

實際上,不同的場景,不同的原始數據,對於索引的需求也會千差萬別。

2.非功能性需求

不管是存儲在內存中還是磁盤中,索引對存儲空間的消耗不能過大。如果存儲在內存中,索引對占用存儲空間的限制就會非常苛刻。畢竟內存空間非常有限,一個中間件啟動后就占用幾個GB的內存,開發者顯然是無法接受的。如果存儲在硬盤中,那索引對占用存儲空間的限制,稍微會放寬一些。但是,我們也不能掉以輕心。因為,有時候,索引對存儲空間消耗會超過數據。

在考慮索引查詢效率的同時,我們還是考慮索引的維護成本。索引的目的是提高查詢效率,但是,基於動態數據集合構建的索引,我們還要考慮到索引的維護成本。因為在原始數據動態增刪改的同時,我們也需要動態的更新索引。而索引的更新勢必會影響到增刪改的操作性能。

構建索引常用的數據結構有哪些?
實際上,常用來構建索引的數據結構,就是我們之前講過的幾種支持動態數據集合的數據結構。比如,散列表、紅黑樹、跳表、B+樹。除此之外,位圖、布隆過濾器可以作為輔助索引,有序數組可以用來對靜態數據構建索引。

我們知道,散列表增刪改查操作的性能非常好,時間復雜度是O(1)。一些鍵值數據庫,比如Redis、Memcache,就是使用散列表來構建索引的。這類索引,一般都構建在內存中。

紅黑樹作為一種常用的平衡二叉查找樹,數據插入、刪除、查找的時間復雜度是O(logn),也非常適合用來構建內存索引。Ext文件系統中,對磁盤塊的索引,用的就是紅黑樹。

B+ 樹比起紅黑樹來說,更加適合構建存儲在磁盤的索引。B+樹是一個多叉樹,所以,以相同個數的數據構建索引,B+樹的高度要低於紅黑樹。當借助索引查詢數據的時候,讀取B+樹索引,需要的磁盤IO次數更少。所以,大部分關系型數據庫的索引,比如MySQL、Oracle,都是用B+樹來實現的。

跳表也支持快速添加、刪除、查找數據。而且,我們通過靈活調整索引結點個數和數據個數之間的比例,可以很好的平衡對內存的消耗及其查詢效率。Redis中的有序集合,就是用跳表來構建的。

除了散列表、紅黑樹、B+樹、跳表之外,位圖和布隆過濾器這兩個數據結構,也可以用於索引中,輔助存儲在磁盤中的索引,加速數據查詢的效率。我們來看下,具體是怎么做的?

布隆過濾器有一定的判錯率。但是,我們可以規避它的短處,發揮它的長處。盡管對於判定存在的數據,有可能並不存在,但是對於判定不存在的數據,那肯定就不存在。而且,布隆過濾器還有一個更大的特點,那就是內存占用非常少。我們可以針對數據,構建一個布隆過濾器,並且存儲在內存中。當要查詢數據的時候,我們可以先通過布隆過濾器,判定是否存在。如果數據不存在,那我們就沒必要讀取磁盤中的索引了。對於數據不存在的情況,數據查詢就更加快速了。

實際上,有序數組也可以被作為索引。如果數據是靜態的,也就是不會插入、刪除、更新操作,那我們可以把數據的關鍵詞(查詢用的)抽取出來,組織成有序數組,然后利用二分查找算法來快速查找數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM