Elastic Search和MySQL的部分對比(持續更新)


我們一般用ES做分布式的實時全文搜索,而考慮在MySQL中也存在全文索引這種類似的東西,今天主要記錄一下這兩者在全文搜索和聯合查詢之間的些許不同

 

MySQL的全文索引與ES的倒排索引

MySQL

在MySQL中我們用fulltext index表示全文索引,用於(可能會用於?反正我不用)全文搜索,具體的用法如下所示

select * from user where match(name,information) aganist ('yza','hahahahah');

其中name和information都要建立全文索引(但是注意其匹配全文索引得有個最長和最短的匹配項的要求)

這種模式在MySQL5.8之前只有MyISAM存儲引擎支持,在5.8之后innodb和myisam都支持全文索引,這種方式比like xxx%會快很多,但是也存在很多的問題。

  1. 首先就是全文索引不支持搜索高亮。
  2. 其次原理類似於分詞,然后存儲,類似於“請叫我yz",就會分為“請叫,叫我,我y,yz”這種兩個兩個的詞,不能動態的分成若干長度的詞,比如我想兩個,三個的分就無法實現(請叫我,yz),另外對於搜索結果會以搜索條件整個單詞作為匹配項,例如如果我要搜索“草莓橘子”,就只會去匹配含有“草莓橘子”的列,不會去考慮匹配“草莓”和“橘子”,這個ES就可以做到。
  3. 然后就是如果一個列上有全文索引則一定會用上,即使有性能更好的其他索引也不會用上。

Elastic Search

而在ES中,對於全文搜索采用的則是倒排索引

什么是倒排索引?簡而言之,我們一般都是去尋找這個文章中出現了哪幾個單詞,頻率是多少,而倒排索引則是記錄某個單詞在哪些文檔中出現過,並記錄此文檔的id和出現的位置,存入所謂的倒排列表中,這就是所謂的倒排索引。

而根據上面的描述,我們很容易得出,倒排索引主要記錄的有兩個,一為出現了哪些單詞,二是這些單詞都出現在哪些文檔中。

  • 對於第一個我們使用詞典存儲

    存儲於內存中,當然也不可能直接存儲每個單詞在內存中,ES對於詞典使用的是類似於字典樹的存儲,但是做了些許優化,主要是單詞共享前綴和共享后綴,這樣就能降低存詞典所用的空間,另外還加入了一些壓縮算法,更進一步的節省內存。

    其次,就算這樣壓縮了每個單詞,其實還是會很大(因為一個文章會有很多的詞語啊),在真實的內存中,只會保存一些單詞的前綴,而通過這個前綴我們可以快速的定位到磁盤真正的詞典塊(以block划分),再去這些塊中去找到對應的單詞(由於是排序好了的,所以說理論上是o(logn)的時間復雜度,當然可能會遇到目標的單詞並不在這一塊上(因為某個前綴可能會有很多個對應項,會存在很多個塊),這個時候再次去遍歷下一塊就好了),這樣就進一步的壓縮了詞典的大小。

  • 對於第二個則是存儲在倒排列表中

    存儲於磁盤上,以段的形式存儲(參考操作系統中文件的分塊和分段)這個主要是考慮同步的問題,盡量避免了沖突而加鎖,因為對於倒排文件,修改一個地方的索引(比如某個文檔的某個單詞被修改,就得從倒排索引中這個單詞所對應的文檔記錄列表中刪去這個文檔的信息,這其實是一個很麻煩的過程,要重新建立一個新的索引,很浪費時間),在使用段了之后,對於一個地方的修改,只用將該段刪除(注意,整個索引其實並不建議真的刪除,所以這里的刪除其實是邏輯刪除,加入.de文件l表示已經被刪除,在執行某次查詢之后會根據.del文件對查詢結果做進一步的過濾)然后再插入一個新的索引就行了,不用重建整個索引。

 

MySQL聯合查詢和ES聯合查詢

MySQL

在MySQL中其實對於聯合查詢的效率比較低(除非建立聯合索引,當然更別說join了)

例如,我們在user表上有兩個索引,一個idx_name,一個idx_information

select * from user where name=''yuanzhe" and information="xxx"

這個時候就只會使用一個索引,不會兩個都是用,而對於第二個條件,則會去內存中的第一個索引查詢結果集合中篩選,所以說其實不是真正的聯合查詢!!!

小擴展:在join中聯合join就更加不堪入目,一般有三種查詢方式,循環嵌套查詢,哈希查詢,排序表查詢。默認會使用循環嵌套查詢

例如   a left join b on a.id =b.id where a.id=‘123456’

這個時候執行情況是,在a中取出符合條件的行,再去b中一個個的查詢(假設a中符合條件 M條,b中有N條)。

如果b的id有索引(基於索引的循環嵌套查詢),那么就還好,時間復雜度也就o(M*logN)(將a的M條一個個拿出,循環去b中查找,因為b的id有索引,所以走b+樹索引的logN,所以說要小表驅動大表啊!!!!!)

如果b的id沒有索引(基於塊的循環嵌套查詢),那么就慘了,只能將a的記錄加載到內存中(join buffer),然后將b的記錄也加載到內存中去對比,時間復雜度o(N*M),而且如果join buffer放不下,還得分塊去,磁盤IO飛起,所以說join的時候還是建建索引吧。

這里記錄一下剩下兩種查詢方式

  • hash查詢

    這種查詢方式的大致思路是,將a中的數據用hash表存儲起來,然后不斷用b的列去比較,由於hash表傾向於o(1)時間復雜度,所以說如果數據分散的合理,沖突不多,其實時間復雜度也就還好,但是哪有這么好的美事呢?

  • 排序表查詢

    這種查詢的大致思路是將a和b的符合條件的數據集合排序,然后就像類似於我們尋找兩個排序數組的公共數字,時間復雜度o(N+M),如果兩張表的數據已經排序過了(建了索引),那么這種方式其實還挺好的,但是如果沒有排序,就得去基於內存分塊排序了(類比於 file sort)。

Elastic Search

對於ES而言,實現真正的聯合索引步驟為:先分別去根據倒排索引查找對應的兩種條件的結果集合,然后將兩種結果集合取並集,這里取並集也有兩種方式:

  • 基於跳表的合並

    基於跳表的合並,其實就是將兩種條件查詢的結果集合A和B,取比較少的那一個(假設A的長度比B長),那么就遍歷B的集合,不斷去A中查找是否存在一樣的值(跳表時間復雜度o(logn),穩定的很),然后將匹配到的值記錄在最終結果集合中,沒有匹配到的記錄就舍棄。

  • 基於bitset的合並

    基於bitset的合並,其實就是使用bitset去記錄哪些記錄存不存在,類似於redis中的bitmap,不過這里ES對於bitset做了一定的優化,因為判斷1<N<10000000的集合中,某個數字是否存在,這種集合如果結果比較少(可能就幾千個,大多數情況下都不會超過總記錄數的1/10),就會形成一種非常稀疏的bitmap,浪費了很多空間。

    就好比某次查詢返回的文檔是1,10000000,如果用原來的bitmap,就需要記錄中間的9999999個0和2個1,這樣內存就不划算。

    ES做的優化是,取結果記錄的前16位作為block的標識,取后16位作為block中表示這個記錄中的某一位,就類似於我們使用65535*M+N去記錄某個文檔是否存在於結果集合中,用(M,N)去記錄,這個時候就節省了很多內存。

 


免責聲明!

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



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