熟悉 MySQL 的同窗必定都知道,MySQL 對於複雜條件查詢的支持並很差。MySQL 最多使用一個條件涉及的索引來過濾,而後剩餘的條件只能在遍歷行過程當中進行內存過濾,對這個過程不瞭解的同窗能夠先行閱讀一下《MySQL複雜where條件分析》。html
上述這種處理複雜條件查詢的方式由於只能經過一個索引進行過濾,因此須要進行大量的 I/O 操做來讀取行數據,並消耗 CPU 進行內存過濾,致使查詢性能的降低。面試
而 ElasticSearch 因其特性,十分適合進行復雜條件查詢,是業界主流的複雜條件查詢場景解決方案,普遍應用於訂單和日誌查詢等場景。sql
下面咱們就一塊兒來看一下,爲何 ElasticSearch 適合進行復雜條件查詢。數據庫
Elasticsearch 是開源的實時分佈式搜索分析引擎,內部使用 Lucene 作索引與搜索。它提供"準實時搜索"能力,而且能動態集羣規模,彈性擴容。數組
Elasticsearch 使用 Lucene 做爲其全文搜索引擎,用於處理純文本的數據,但 Lucene 只是一個庫,提供創建索引、執行搜索等接口,但不包含分佈式服務,這些正是 Elasticsearch 作的。緩存
下面,咱們來介紹一下 ElasticSearch 的相關概念。爲了便於初學者理解,咱們先將 ElasticSearch 中的概念和 MySQL 中的概念大體地進行對應。可是兩者在具體細節上仍是有不少差別的,你們深刻了解 ElasticSearch 就會將兩者區分清楚,不能強行對比等同。數據結構
ElasticSearch 還有一系列有關其分佈式特性的概念,咱們這裏就暫不介紹了,等後續學習到其分佈式特性時在進行介紹。app
MySQL 有 B+ 樹索引,而 ElasticSearch 則是倒排索引 (Inverted Index),它經過倒排索引來實現比 MySQL 更快的過濾和複雜條件的查詢,此外,全文搜索功能也是依賴倒排索引才能實現。下面,咱們就具體來看一下何爲倒排索引。nosql
倒排索引按照維基百科的描述,是存儲文檔內容到文檔位置映射關係的數據庫索引結構。不過只看定義,我是有點迷惑,這不是和 MySQL 的非主鍵索引相似嘛,爲何要叫它「倒排」呢?這個問題我目前也爲搞清楚,可能要等到後續瞭解了其具體實現才能理解。elasticsearch
咱們仍是以書籍檢索爲例,假設有如下數據,每一行就是一個 Document,每一個 Document 由 id,ISBN 號,做者名稱和評分組成。
給上述數據按照 ISBN 和 Author 創建的倒排索引以下所示。倒排索引是每一個字段分開創建的,相互獨立。有兩個專門的術語,分別是索引 Term 和倒排表 Posting List。字段的值就是 Term,好比 N0007,而 Term 對應的文檔 ID 的列表就是 Posting List,對應圖中紅色的部分。
通常 Term 都是按照順序排序的,好比 Author 名稱就是按照字母序進行了排序,排序以後,當咱們搜索某一個 Term 時,就不須要從頭遍歷,而是採用二分查找。一系列排序後的 Term 就組成了索引表 Term Dictionary。
可是 Term Dictionary 每每很大,沒法完整放入內存,這是爲了更快的查詢,還須要再給它建立索引,也就是 Term Index 。
ElasticSearch 使用 Burst-Trie 結構來實現 Term Index,它是一種前綴樹 Trie 的一種變種,它主要是將後綴進行了壓縮,下降了Trie的高度,從而獲取更好查詢性能。
Term Index 並不須要像 MySQL 的索引同樣,包含全部的 Term,而是包含的是這些 Term 的前綴。它就相似於字典的查詢目錄,能夠進行快速定位到 Term Dictionary 的某一位置,而後再從這個位置向後查詢。
綜上, Alice,Alf,Arlan,Bob,Tom 等詞的倒排索引以下所示。綠色部分是 Term Index,藍色部分是 Term Dictionary,紅色部分是 Posting List。
通常來講,Term Index 都是所有緩存在內存中,查詢時,先經過其快速定位到 Term Dictionary 對應的大體範圍,而後再進行磁盤讀取查找對應的 Term,這樣就大大減小了磁盤 I/O 的次數。
瞭解了 ElasticSearch 的倒排索引後,咱們再來看看其如何處理複雜的聯合索引查詢。好比上述書籍例子中,咱們須要查詢評分等於2.2而且做者名稱叫 Tom的書籍。
理論上,咱們只須要分別按照 Score 和 Author 字段的倒排索引進行查詢,獲取響應的 Posting List,再將其作交集合並便可。
這裏又要吐槽一下 MySQL,它是不支持這個合併操做的,它只能按照一個字段的索引進行查詢,而後根據另一個字段的條件作內存過濾。順便說一下,MySQL 的 join 功能也弱爆了,感興趣的同窗能夠了解一下這篇文章
而 ElasticSearch 則支持使用跳錶 Skip List和 Bitset 的方式將數據集進行合併。
ElasticSearch 在存儲 Posting List 數據時,就保存了對應的多級跳錶結構響應的數據,這也體現了其空間換時間的基本思想。
這裏先介紹一下跳錶的基本概念,它實際上是一種能夠進行二分查找的有序鏈表。跳錶在原有的有序鏈表上面增長了多級索引,經過索引來實現快速查找。首先在最高級索引上查找最後一個小於當前查找元素的位置,而後再跳到次高級索引繼續查找,直到跳到最底層爲止,經過這種方式,加快了查詢的速度。
好比,按照 Score 查出來的 Posting List 爲[2,3,4,5,7,9,10,11],按照 Author 查出來的結果爲 [3,8,9,12,13],則兩者的跳錶結構以下圖所示。
具體合併過程則是先選最短的 posting list,也就是 Author 的結果集,從其最小的一個 id 開始,將其做爲當前最大值。而後依次剩餘 posting list 中查找大於或等於該值的位置。
好比上述結果集中,先去 Score 結果集中查找 3,找到後,就代表 3是兩者的合集元素之一;而後再從新開啓一輪,選取 Author 結果集中 3 的下一個值 8 ,去 Score 結果集查詢 8,發現了大於等於 8 的最小的值是 9 ,因此不可能有共同的值 8,而後再去 Author 結果集查找 9 ,發現其大於等於 9 的最小值是 12,因此再去 Score 結果集中查找大於等於 12的值,發現並不存在;最終得出兩者的合集就只有[3]。
在查詢過程當中,每一個 posting list 均可以根據當前 id 經過 skip list 快速跳過不符合的 id 值,加速整個合併取交集的過程。
ElasticSearch 對於較長的 posting list 也會使用 Frame Of Reference 進行壓縮編碼,減小了磁盤佔用,減小了索引尺寸。有關具體存儲結構的實現咱們後續再進行細聊。
ElasticSearch除了使用 skipList 來進行數據磁盤讀取時的合併操做外,還會將一些查詢條件對應的結果集 posting list 進行內存緩存,也就是所謂的 Filter Cache,爲了後續再次複用。
爲了減小內存緩存所消耗的內存空間大小,ElasticSearch 沒有使用單純的數組和 bitset 來存儲 posting list,而是使用要壓縮效率更高的 Roaring Bitmap。
咱們能夠先來說一下單純數組或 bitset 數據結構爲何並不使用。好比以下一道較爲常見的面試題目:
給定含有40億個不重複的位於[0, 2^32 - 1]區間內的整數的集合,如何快速斷定某個數是否在該集合內?
若是咱們要使用 unsigned long 數組來存儲它的話,也就須要消耗 40億 * 32 位 = 160 Byte,大體是 16000 MB。
若是要使用位圖 Bitset 來存儲的話,即某個數位於原集合內,就將它對應的位圖內的比特置爲1,不然保持爲0。這樣只須要消耗 2 ^ 32 位 = 512 MB,這可只有原來的 3.2 % 左右。
可是,Bitset 也有其缺陷,也就是稀疏存儲的問題,好比上述集合並非 40億,而是隻有2,3個,那麼 Bitset 中只有少數幾位是1,其餘位都是 0,可是它仍然佔用了 512 MB。
而 RoaringBitmap 就是爲了解決稀疏存儲的問題。下圖就是 RoaringBitmap 的基本原理示意圖。
首先,如上圖所示,計算出32位無符號整數和 65536 的除數和餘數。其含義表示,將32位無符號整數按照高16位分桶,即最多可能有2^16=65536個桶,術語懲治爲 container。存儲數據時,按照數據的高16位找到 container(找不到就會新建一個),再將低16位放入container中。也就是說,一個 RoaringBitmap 就是不少container的集合。
而後 container 內具體的存儲結構要根據存入其內數據的基數來決定。
基數小於 2 ^ 12 次方即 4096時,使用unsigned short類型的有序數組來存儲,最大消耗空間就是 8 KB。
基數大於 4096 時,則使用大小爲 2 ^ 16 次方的普通 bitset 來存儲,固定消耗 8 KB。固然,有些時候也會對 bitset 進行行程長度編碼(RLE)壓縮,進一步減小空間佔用。
ElasticSearch 就是使用 Roaring Bitmap 來緩存不一樣條件查詢出來的 posting list,而後再進行與操做計算出最終結果集。
至此,咱們也算了解了 ElasticSearch 爲何比 MySQL 更適合複雜條件查詢,可是有好就有弊,由於爲了查詢作了這麼多的準備工做,ElasticSearch 的插入速度就會慢於 MySQL,並且數據存入ES後並非立馬就能檢索到。
歡迎持續關注歷小冰,後續繼續爲你們分享 MySQL、Redis 和 ElasticSearch 等數據庫相關的原理和實踐經驗。