之前的工作關系,需要在手機上支持中文和拼音搜索。由於手機上存儲數據一般都是用 sqlite,所以是基於 sqlite3 fts5 來實現。這段時間再次入門 c++,所以想用 c++ 實現一下,一來用於練手,二來當時做的時候發現網絡上這方面開源的實現不多,也造福下其他人。
背景
搜索現在幾乎是每個 APP 必備的功能,用戶已經習慣了搜索框搜一下,避免到處去找。搜索也是幫助用戶查找舊信息,發現新功能的一個重要手段。平常我們用微信的時候經常會搜索聯系人和聊天記錄,發現微信這一塊做的還是非常好的。關於微信的全文搜索,可以看看這兩篇文章:微信全文搜索優化之路 和 微信移動端的全文檢索多音字問題解決方案 。
第一篇文章主要是問題和原理的概述,第二篇文章是核心分詞器的實現。我寫的這個項目主要是實現了 simple 分詞器,並提供一些輔助函數幫助使用。
Simple 分詞器
搜索的核心是建倒排索引,建索引的核心是分詞器。 跟名字一下,Simple 分詞器的規則非常簡單:
- 空白符跳過
- 連續的數字作為整體是一個索引
- 連續的英文字母作為整體並轉換成小寫索引
- 中文字單獨建索引,並且把中文字轉成拼音后也建搜索,這樣就能同時支持中文和拼音檢索。另外把拼音首字母也建索引,這樣搜索 zjl 就能命中 “周傑倫”。
- 其他字符統一單獨建索引,這樣搜索 😊 也能搜到
上面的 5 條都比較好理解,關於中文為什么這么做(而不是連續的中文一起建索引),是由於客戶端搜索的需求決定的。具體可以參考上面微信的兩篇文章。
有了上面的規則,代碼寫起來就很簡單了,核心邏輯 30 行就解決了。這塊代碼運行效率也比較高,一遍掃描 O(n) 的復雜度就完成了分詞操作。
query 拆分
索引建好之后,query 需要根據分詞規則來寫才能查詢到數據。比如根據上面的邏輯:
- 如果查數字,我們要把搜索詞當作前綴來用,比如用戶搜索 123, query 就需要換成 123*,這樣如果索引里面有 12345 也能被搜索出來
- 對於英文,除了要當作前綴,還需要把搜索詞轉成小寫,比如用護搜索 Hello,query 就需要換成 hello*, 這樣如果索引里面有 HelloWorld 也能被命中
- 對於中文和其他字符,都要拆成單個的才能命中索引
- 最后對於拼音(其實我們沒辦法區分英文和拼音,統一當作拼音處理就行),需要把拼音按照規則拆分,因為我們的拼音索引是單字建立的。這樣如果用戶搜索 “zhangliangy”,拼音就可以被拆成 ‘zhang AND liang AND y*',從而命中"張靚穎”。具體規則微信的文章中也有詳述。
可以看到 query 詞重構的邏輯也比較多,在之前的項目中沒有好的辦法,所以是自己在應用層代碼里面組裝好了 query 再給 sqlite 去搜的,這樣其實不太方便。在這個項目中,我實現了一個 simple_query 的字符串函數,輸入一個 string,它會給轉換成組裝好的搜索詞,用法跟使用 sqlite 內置函數一樣,這樣就方便很多了,下面是一個例子:
可以看到, match 后面用 simple_query 這個函數,傳入用戶輸入的搜索詞就可以用了。
另外 sql 中還有一個 simple_highlight 函數,它的作用和內置的 highlight 函數一樣,只是它會把連續命中的詞一起高亮。比如對於文檔"周傑倫”,如果搜索詞是 ‘zhou AND jie’,那么 highlight 函數會返回 “[周][傑]倫”,simple_highlight 會返回 “[周傑]倫”。
總結
最后說幾句關於 sqlite fts5 的使用的問題。個人建議通過 trigger 的方式來維護索引的這張表,具體使用的方式可以在官方文章中搜索 trigger 找到例子。這樣使用的好處是沒有復雜的邏輯去保證文檔數據和索引數據一致,微信的文章中很大一部分復雜度在描述怎么保證數據一致的問題。他們可能有自己的業務復雜性,但是對於一般的場景來說, trigger 是最好的方式。
從這個項目我們能學到:
- 怎么給 sqlite3 做一個支持中文和拼音的 fts5 拓展
- 怎么給 sqlite3 添加用戶自定義的函數
- 在一個項目中同時使用 c 和 c++ ,並合理處理邊界問題
大家可以下載使用,也可以根據自己的需求去改進,定制更多的函數和策略。
Reference
- Simple 分詞器: https://github.com/wangfenjin/simple
- sqlite 官方文檔:https://www.sqlite.org/fts5.html
- 微信全文搜索優化之路:https://juejin.im/entry/59e6cd266fb9a0451968ab02
- 微信移動端的全文檢索多音字問題解決方案:https://cloud.tencent.com/developer/article/1198371