Simple: SQLite3 中文結巴分詞插件


一年前開發 simple 分詞器,實現了微信在兩篇文章中描述的,基於 SQLite 支持中文和拼音的搜索方案。具體背景參見這篇文章。項目發布后受到了一些朋友的關注,后續也發布了一些改進,提升了項目易用性。

最近重新體驗微信客戶端搜索功能,發現對於中文的搜索已經不是基於單字命中,而是更精准的基於詞組。比如搜索“法國”,之前如果句子中有“法”和“國”兩個字時也會命中,所以如果一句話里包含“國法”就會被命中,但是這跟“法國”沒有任何關系。

本文描述對 simple 分詞器添加的基於詞組命中的實現,從而實現更好的查找效果。另外本文也會基於之前在 issue 中大家提到的問題,提供一個怎么使用 SQLite FTS 表的建議。

背景

先簡單回顧一下之前的實現,因為結巴分詞只跟中文有關,所以本文會略去拼音的部分。

搜索主要分為兩部分,建立索引和命中索引。為了實現中文的搜索,我們先把句子按照單字拆分,按照單字建立索引;然后對於用戶的輸入,也同樣按照單字拆分,這樣 query 就能命中索引了。為了支持詞組搜索,再按照單字拆分就很難滿足需求了,所以可以考慮的方案是要么改索引,要么改 query。如果改索引的話會有一些問題,比如如果用戶就輸入了一個字比如“國”,但是我們建索引的時候把“法國”放到了一起,那“國”字就命中不了了,所以最好是保持單字索引不變,通過改寫 query 來達到檢索詞組的效果。

實現

simple 分詞器之前提供了一個 simple_query() 函數來幫助用戶生成 query,我們也可以加一個新的函數來實現詞組的功能。經過簡單的調研,我們發現 cppjieba 用 C++ 實現了結巴分詞的功能,很適用於我們的需求。

所以我實現了一個新的函數叫做 jieba_query() ,它的使用方式跟 simple_query() 一樣,內部實現時,我們會先使用 cppjieba 對輸入進行分詞,再根據分詞的結果構建 SQLite3 能理解的 query ,從而實現了詞組匹配的功能。具體的邏輯可以參考 這里 。對於不需要結巴分詞功能的用戶,可以在編譯的時候使用 -DSIMPLE_WITH_JIEBA=OFF 關閉結巴分詞的功能,這樣能減少編譯文件的大小,方便客戶端對文件大小敏感的場景使用。

使用

本文想着重介紹一下 SQLite3 FTS5 功能使用的問題,這些問題都是有朋友在項目的 issue 中提到過的,都是非常好的問題,但是也說明有不少人對怎么使用 FTS 表不太清楚,希望本文能解決一些疑惑。

首先第一點,FTS5 表雖然是一個虛擬表,提供了全文搜索的功能,但是它整體還是跳不出 SQL 的范疇,所以其實很多用法和其他 SQL 表是一樣的,當然它也跳不出 SQL 的限制。比如有一個 issue 問如果表中有多列的時候,能不能檢索全表,但是只返回命中的那些列?答案是不行的,因為按照 SQL 的語法規則,SELECT 語句后面必須顯示說明你想要 SELECT 哪些列,所以結果列是必須用戶指定的,如果我們像知道哪些列命中了,只能通過其他一些手段,感興趣的朋友可以看這個 issue36

另外 simple 分詞器提供了不少額外的功能,比如 simple_query() 和 simple_highlight() 等輔助函數,但是它並不影響我們使用原有 FTS5 的功能,比如如果想按照相關度排序,FTS5 自帶的 order by rank 功能還是可以繼續可以使用,也就是說 FTS5 頁面 提供的所有功能都是可以和 simple 分詞器一起使用的。

最后也是最重要的一個問題,FTS5 表到底該怎么用?有一個 issue26 提到的問題非常好,我把它放到這里:

《微信全文搜索優化之路》一文中針對索引表的介紹,我對索引有幾個問題想請教一下:

  1. 業務表是正常的程序的數據表,還要再為了全文搜索再多建立一份索引表,是嗎?我直接將我的業務數據表在創建的時候按虛表建立行嗎?(例如create virtual table tablename using fts5(列名1,列名2,tokenize = 'simple'))
  2. 如果再多建立一份索引表,那是不是每一個業務表和對應的索引表的表字段是完全相同?
  3. 如果再多建立一份索引表,那數據庫的大小是不是加倍了,尤其是把文件或圖片影片存入數據庫的情況(BLOB類型)?
  4. 原文中【為了解決業務變化而帶來的表結構修改問題,微信把業務屬性數字化】,這也是我想要的,能否幫助貼下原文中提到的【索引表-IndexTable】和【數據表-MetaTable】的建表語句?

核心的問題是:想讓表數據支持全文搜索,需要把數據復制一份嗎?這樣會不會導致數據庫膨脹?在用戶的手機上我們可不想占用太多無謂的空間。

externel content table

其實這個問題在 FTS5 的官方文檔中已經給出了解決方案那就是 externel content table,大家也可以參考 這篇文章

它的意思是我們可以建一張普通表,然后再建一張 FTS5 表來支持全文索引,這張虛擬表本身不會存儲真實的數據,如果 SELECT 語句用到具體的內容,都會通過關聯關系去原表獲取,這樣就不存在數據重復的問題了。但是這里就會涉及到數據一致性的問題,怎么保證原表的數據和索引表是一致的呢?通過 trigger 也就是觸發器來實現:對於原表的增刪改,都會通過觸發器同步到 FTS 表。這樣基本上就完美解決了上面用戶的問題。

可能有人會問為什么不直接用 FTS5 表呢?這樣普通表就不用了,也省了觸發器的邏輯。原因是 FTS 表提供了全文索引的能力,但是它也有限制,對於基於 ID 或者其他普通索引的請求它是不支持的,如果我們想有一個時間列並且基於時間列索引排序,FTS表就不行,還是需要普通表。通過普通表和 FTS 表結合的方案,我們就能同時使用兩者的能力。

微信的方案

需要注意的是,微信並沒有使用上面提到的方案,而是單獨建了一張打平的索引表,把所有需要全文索引的數據放到一張單獨的表里面,再通過外鍵關聯到具體的業務去。這樣的好處在微信的文章中有所提及,主要是其他關聯的表結構變更的時候,FTS 表不用動,這樣很容易添加想要搜索的字段,只需把該字段寫入 FTS 表及關聯關系的表就行,表結構見下圖:

wechat-fts5

個人覺得對於微信這個復雜度的業務,可以考慮這個方案,畢竟需要搜索的信息非常多,這樣方便各個業務復用搜索能力。但是對於大部分的業務,用 external content table 可能是更簡單的方案,畢竟在數據寫入和讀取的時候都更快更方便,微信的方案在數據操作流程上會復雜不少,需要邏輯上做更多的封裝。

總結

上面主要介紹了 simple 分詞器最新的功能,基於結巴分詞實現基於詞組的搜索功能,實現更精准的匹配。另外也介紹了在實際項目中使用 FTS 表的方案,希望對大家有所助益。

Reference


免責聲明!

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



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