關係型SQL的全文搜尋 FullText 方案先行探究分析

近段時間一直在對全文搜尋引擎的方案進行調查,核心問題就是現行使用的Select...Like%$1%的模糊搜尋語句在高並行的場景下性能與效能非常差,雖然我已經做了相應的調優,但無奈終究不是長久之計。

實際上在 Web 領域經歷了 15 年的發展,PHP 從版本 4 時代的龜速發展到現在的 PHP7 的高效,再加上其他語言例如 Go 與 Rust 之類的高效語言的出現,在 Web 開發領域中的瓶頸已經從以前的 Web 程式變爲 SQL。當然在 15 年前非常有意思的一個現象是,因爲當時 InnoDB 還未出生並無事務(Transaction)這麽一説,且因當時的 Web 程式碼效能普遍低於 SQL,所以當時很多程式開發都濫用了存儲過程來幫助 Web 程式提高效能。但這都是 15 年前的事情了,現在 SQL 早已成爲效能瓶頸,就別再給 SQL 增加負擔了。

雖然現在已經有很多的 NoSQL 非關係型 SQL 的問世,無奈關係型 SQL 還是在如今占據大半江山。然而在 15 年中關係型 SQL 并未有多大的改進,關係型 SQL 的理論已經發展到頂,很難再有非常大的改觀。

以下的調查均建立在主要以搜尋(Select)爲主的場景中。

如果是以搜尋爲主的場景中,通常我們都會使用 MyISAM 作爲存儲引擎。我們假設在一個有 100 萬數據條目的表中執行一個較爲複雜的搜尋語句:

SELECT `column` FROM `table` USE INDEX(field_index) WHERE 1=1 AND (`field` LIKE '%keyword1%' OR `field` LIKE '%keyword2%') AND (`field` LIKE '%keyword3%' OR `field` LIKE '%keyword4%') AND (`field` LIKE '%keyword5%' OR `field` LIKE '%keyword6%') LIMIT 50;

類似如此的語句一執行,有多少個關鍵詞就要掃多少次全表,就算 MyISAM 效能再好也都要占用將近 30% 的 CPU 資源並使用將近 1 秒的時間。

很明顯我們已經盡力了。這種情況下我們很難再進行調優獲得更好的效能。但很剛好,這種大多使用搜尋的使用場景下,卻是搜尋引擎的强項。

一般在現在,常見的針對全文搜尋的方案差不多有以下幾種:

  • MySQL 内建的 FullText 全文搜尋索引
  • MySQL 外挂的 Bigram FullText Plugin
  • 各種全文搜尋引擎

以上三種方案我們先來討論前兩種方案。


  • MySQL 内建的 FullText 全文搜尋索引

首先,這個内建的 FullText 全文搜尋索引對 CJK 語言(中國語 Chinese,日本語 Japanese,韓國語 Korean)的支持在低於 5.7 版本的 MySQL 中幾乎等於沒有。在 MySQL5.7 后引用了 N -GRAM 演算法去斷詞所以對 CJK 有了較好的支持。然而此索引雖然支持了全文搜尋,但在關鍵問題上依舊無明顯改善,CPU 占用依舊居高不下,且搜尋時間依舊很長。本身此機能是依靠 MySQL 自身建立一個 FullText 索引來進行搜尋,然而有一個問題就是在修改數據的時候,又要重新更新索引。這裏的性能損失就會非常的離譜。


  • MySQL 外挂的 Bigram FullText Plugin

這個外挂插件一開始就引用了 N -GRAM 演算法去斷詞所以對 CJK 有較好的支持。然而一個很嚴重的問題是,如果你的數據條目超過 20W 或表超過 200MB,那更新索引的時候將會占用大量的 CPU 資源,并且能肉眼感覺出這更新速度十分龜速。在 20W 條目下,建立索引就已經超過 10 分鐘。實時更新索引的時候 CPU 占用更是居高不下。


很明顯以上兩種親和 MySQL 的方案都無法實際使用,所以我的目光直接開始搜尋各種全文搜尋引擎。

目前常見的搜尋引擎我大致歸類為以下三派:

  • Sphinx Search 派系
  • Apache Lucene 派系
  • 野路子 N -GRAM 派系

由於野路子 N -GRAM 派系的程式大多娛樂成分較高,所以基本上就不會考慮。接下來著重介紹一下 Sphinx Search 派系和 Apache Lucene 派系。

Sphinx Search 派系

Sphinx Search 是一款俄國人出品的全文搜尋程式,使用 C ++ 程式碼編寫。特點就是十分高效,速度也非常快。同時 Sphinx Search 衍生出了一堆改進品,可謂是此派系的鼻祖。然而,Sphinx Search 在 3.0 版本之後選擇了閉源,Sphinx Search 本身就開始走向衰敗開始鮮有人問津,但從 3.0 以前的版本衍生出來的各種搜尋引擎依舊在各自領域發光發熱。

Sphinx Search 派系差不多有以下成員:

  • Sphinx Search

優點:性能很好,資源占用非常低。

缺點:局限性挺大,只適合搜尋操作,只適合與 MySQL,MSSQL 等關係型 SQL 搭配使用。默認對 cjk 的支持很差,配置文件略麻煩,且現在已經閉源。

  • MantiCore Search

優點:由於是 Sphinx Search 閉源前 Fork 過來的項目,所以繼承了 Sphinx 的所有優點,並自行添加了 cjk 的支持,雖然可以直接使用 sphinx search 的配置文件,但也支持了一套新寫法,較爲簡單化。性能很好,資源占用非常低。

缺點:還是由於是 Sphinx Search 閉源前 Fork 過來的項目,所以還是繼承了一部分的缺點。例如局限性挺大,只適合搜尋操作,只適合與 MySQL,MSSQL 等關係型 SQL 搭配使用。

  • Senna

優點:由於是日本人對 Sphinx Search 的 CJK 支持感到不滿而 Fork 過來的改良品,所以 Sphinx 的優點他也有。也支持了 CJK。性能很好。

缺點:Sphinx 系列同樣的缺點,局限性大。且最重要的是已經很久沒人更新了。

  • Groonga

其實說其是 Sphinx Search 派系的,但已經與 SphinxSearch 有了天壤之別。倒不如説這已經開始產生獨立的一個派系了。

優點:效能好,對 CJK 支持很好。

缺點:還是有著一定的局限性。

  • Mroonga

Mroonga 已經是作爲 MySQL 系列的插件直接與 SQL 有著更好的聯動了,但實際上内核使用的是 Groonga。可以這麽理解,Mroonga 是 Groonga 的 MySQL 外挂包裝版,本身作爲一個 FullText Index 的全兼容替代品,能兼容 MySQL 的大部分操作。通過調整能完美取代 LIKE 的模糊查詢并且效能非常好。

優點:在 MariaDB 10.0.15 版本后自帶了 Mroonga 引擎,且能輕鬆的安裝進 MySQL 與 MariaDB 或者 Percona 中。本身作爲一個 MySQL 外挂,甚至還能作爲一種存儲引擎,與 MySQL 的結合非常緊密。使用了之後根本無需操心數據同步的問題。主要也是日本人負責開發與維護,所以對 CJK 支持的很好,性能也同樣很優秀。

缺點:如果不對搜尋語句進行調優的話,搜尋效能甚至不如 MyISAM。

  • PostgreSQL

著名開源拖拉機,也是這一票 Sphinx 派系中最能打的,但 PGSQL 并不是一開始就是 Sphinx 的成員,最開始的時候 PGSQL 使用了 Apache Lucene 内核作爲全文搜尋引擎,但經過了幾個版本后更換爲類似於 Sphinx 的内核。雖然他是個 SQL,全文搜尋引擎只是個附屬品,但如果條件允許的話直接使用 PostgreSQL 以上問題你都不用考慮了。

優點:性能很好,穩定性也很好。CJK 的支持也有。

缺點:好像真的找不到什麽缺點。


以上就是 Sphinx 派系的常見成員。真正要我推薦的話我建議在 MantiCore Search,Mroonga 與 PostgreSQL 三者中進行考慮。由於 MariaDB 10.0.15 以後的版本都自帶了 Mroonga 引擎,所以爲了省事的話我建議直接使用 MariaDB 10.0.15 以後的版本或者 PostgreSQL。之後我會專門出文對 MantiCore Search 與 Mroonga 進行詳解。


Apache Lucene 派系

Apache Lucene 實際上僅為一個 Java 程式碼中的一個 Class,Apache Lucene 無法自行工作,都需要另行包裝后才能使用。同時此 Class 還有幾個衍生品例如 Lucene.NET 與 Apache Lucy 的封裝,能讓 Lucene 使用在.Net 與 C 程式碼中。但在這裏我們只討論兩個使用 Lucene 為内核的搜尋程式 Solr 與 Elasticsearch。但可以直接就在此告訴大家,Solr 與 ElasticSearch 都無法與 Sphinx Search 派系的產品類似與 SQL 的結合非常緊密。所以數據的錄入與同步是個需要詳細考慮的問題。但也正由於這點,也讓 Solr 與 ElasticSearch 不被局限于關係型 SQL 的那一套理論中。

但由於,我本人是 Java 程式碼歧視主義者,所以我并不推薦此派系中的任何一個。除非他們使用其他程式碼重構。

  • Apache Solr

優點:Solr 的優點應該說是長年來的企業應用帶來的穩定性非常高。

缺點:由於使用了 Java 進行開發,在系統資源占用上存在非常大的浪費。記憶體要求較高,建議給其分配 32GB 的記憶體,但不要超過 32GB。可擴展性與配置難度難度高於 ElasticSearch。生態與 ElasticSearch 相比也較差。

  • ElasticSearch

優點:社區生態應該是本文中的所有產品裏最好的。同時分析功能完全,雖然原生對 CJK 的支持較差但能通過加置各種外挂實現各種功能。RESTful API 的完整支持可以讓你輕鬆的構建出一個 API 伺服器。

缺點:還是由於使用了 Java 進行開發,在系統資源占用上存在非常大的浪費。記憶體要求較高,建議給其分配 32GB 的記憶體,但不用超過 32GB,爲什麽呢?因為多了會造成 CPU 效能嚴重下降。當然這問題並不是 ES 的問題,歸根到底都是 JVM 的記憶體管理策略留下的疑難雜症。同時是優點也是缺點的 RESTful API 支持,交互只能使用 RESTful API,所以必須重新為 ES 編寫相應的程式碼。如果不是新的專案的話,遷移至 ES 的工作量實在有點大。再來,社區版本的 ES 明確寫明不要拿來作爲本地存儲 SQL 使用,因爲可靠性并不行。那非要使用的話怎麽辦?也不是沒辦法,請購買商業版本。最後,就是 ES 最令人頭疼的問題,如果你是同時使用 SQL 與 ES 的話,那 SQL 與 ES 的數據同步基本簡單不了。以前有多種方案來實現 SQL 與 ES 的數據同步,但都有各種各樣的問題,可靠性非常差。那麽怎麽辦?我的建議是,在程式操作 SQL 的同時,也對 ES 進行同樣的操作。


以上就是 Apache Lucene 派系的兩名大將。這個派系的共同點就是,與 SQL 的關係基本沒有。不管是 Solr 亦或者是 ElasticSearch 他們對數據存儲的實現都類似於 NoSQL,但遠遠沒有要求其的可靠性。當然與 SQL 關係不緊密固然也有好處,比如在做 Data Analyzer 能想出更多簡單的辦法。雖然我對 ES 也很有研究,但我并不會推薦各位一上來就考慮 ES。除非是沒有其他任何最優選項的時候,再來考慮 ES。


總結

最開始我的調查分析目的就是要尋找一個能取代 LIKE 模糊查詢的方案,按照我們都喜歡減少工作量的做法來看,我肯定只會從 SphinxSearch 派系中的產品進行選擇。

但如果,你使用的 SQL 為 NoSQL 類型的話,同時你的產品又以 API 爲主的 MicroServices 風格,那不妨可以考慮一下使用 ElasticSearch。

然而如果你與我一樣,使用的是關係型 SQL 并且只是想尋找一個 LIKE 模糊查詢的取代方案,那我可以給你一個以上工具的性能排行大致為什麽樣:

MantiCore Search ≈ Sphinx Search ≈ PostgreSQL ≈ Senna > Groonga ≈ Mroonga > ElasticSearch > Solr > BigGram > FullText Index

當然,我個人比較推薦 MantiCore Search 與 PostgreSQL 和 Mroonga,因爲他們其實真的很棒。之後我會專門出文詳解 MantiCore Search 與 Mroonga,爲什麽是這兩個呢?因爲他們兩個的文真的太少了,不管什麽語言地區對其有深入研究的文章幾乎都看不到。至於 ES 和 PostgreSQL 的話,生態還挺好的,大家基本搜搜都能找到自己想要的内容。