Simple: 一個支持中文和拼音搜索的 sqlite fts5插件


之前的工作關系,需要在手機上支持中文和拼音搜索。由於手機上存儲數據一般都是用 sqlite,所以是基於 sqlite3 fts5 來實現。這段時間再次入門 c++,所以想用 c++ 實現一下,一來用於練手,二來當時做的時候發現網絡上這方面開源的實現不多,也造福下其他人。

背景

搜索現在幾乎是每個 APP 必備的功能,用戶已經習慣了搜索框搜一下,避免到處去找。搜索也是幫助用戶查找舊信息,發現新功能的一個重要手段。平常我們用微信的時候經常會搜索聯系人和聊天記錄,發現微信這一塊做的還是非常好的。關於微信的全文搜索,可以看看這兩篇文章:微信全文搜索優化之路 和 微信移動端的全文檢索多音字問題解決方案 。

第一篇文章主要是問題和原理的概述,第二篇文章是核心分詞器的實現。我寫的這個項目主要是實現了 simple 分詞器,並提供一些輔助函數幫助使用。

Simple 分詞器

搜索的核心是建倒排索引,建索引的核心是分詞器。 跟名字一下,Simple 分詞器的規則非常簡單:

  1. 空白符跳過
  2. 連續的數字作為整體是一個索引
  3. 連續的英文字母作為整體並轉換成小寫索引
  4. 中文字單獨建索引,並且把中文字轉成拼音后也建搜索,這樣就能同時支持中文和拼音檢索。另外把拼音首字母也建索引,這樣搜索 zjl 就能命中 “周傑倫”。
  5. 其他字符統一單獨建索引,這樣搜索 😊 也能搜到

上面的 5 條都比較好理解,關於中文為什么這么做(而不是連續的中文一起建索引),是由於客戶端搜索的需求決定的。具體可以參考上面微信的兩篇文章。

有了上面的規則,代碼寫起來就很簡單了,核心邏輯 30 行就解決了。這塊代碼運行效率也比較高,一遍掃描 O(n) 的復雜度就完成了分詞操作。

query 拆分

索引建好之后,query 需要根據分詞規則來寫才能查詢到數據。比如根據上面的邏輯:

  1. 如果查數字,我們要把搜索詞當作前綴來用,比如用戶搜索 123, query 就需要換成 123*,這樣如果索引里面有 12345 也能被搜索出來
  2. 對於英文,除了要當作前綴,還需要把搜索詞轉成小寫,比如用護搜索 Hello,query 就需要換成 hello*, 這樣如果索引里面有 HelloWorld 也能被命中
  3. 對於中文和其他字符,都要拆成單個的才能命中索引
  4. 最后對於拼音(其實我們沒辦法區分英文和拼音,統一當作拼音處理就行),需要把拼音按照規則拆分,因為我們的拼音索引是單字建立的。這樣如果用戶搜索 “zhangliangy”,拼音就可以被拆成 ‘zhang AND liang AND y*',從而命中"張靚穎”。具體規則微信的文章中也有詳述。

可以看到 query 詞重構的邏輯也比較多,在之前的項目中沒有好的辦法,所以是自己在應用層代碼里面組裝好了 query 再給 sqlite 去搜的,這樣其實不太方便。在這個項目中,我實現了一個 simple_query 的字符串函數,輸入一個 string,它會給轉換成組裝好的搜索詞,用法跟使用 sqlite 內置函數一樣,這樣就方便很多了,下面是一個例子:

-- 完整例子:https://github.com/wangfenjin/simple/blob/master/test.sql -- load so file .load libsimple.so -- set tokenize to simple CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = "simple"); -- add some values into the table insert into t1 values ("周傑倫 Jay Chou:最美的不是下雨天,是曾與你躲過雨的屋檐"), -- query result: [周傑倫] Jay Chou:最美的不是下雨天,是曾與你躲過雨的屋檐 select simple_highlight(t1, 0, '[', ']') from t1 where x match simple_query('zhoujiel');

可以看到, match 后面用 simple_query 這個函數,傳入用戶輸入的搜索詞就可以用了。

另外 sql 中還有一個 simple_highlight 函數,它的作用和內置的 highlight 函數一樣,只是它會把連續命中的詞一起高亮。比如對於文檔"周傑倫”,如果搜索詞是 ‘zhou AND jie’,那么 highlight 函數會返回 “[周][傑]倫”,simple_highlight 會返回 “[周傑]倫”。

總結

最后說幾句關於 sqlite fts5 的使用的問題。個人建議通過 trigger 的方式來維護索引的這張表,具體使用的方式可以在官方文章中搜索 trigger 找到例子。這樣使用的好處是沒有復雜的邏輯去保證文檔數據和索引數據一致,微信的文章中很大一部分復雜度在描述怎么保證數據一致的問題。他們可能有自己的業務復雜性,但是對於一般的場景來說, trigger 是最好的方式。

從這個項目我們能學到:

  1. 怎么給 sqlite3 做一個支持中文和拼音的 fts5 拓展
  2. 怎么給 sqlite3 添加用戶自定義的函數
  3. 在一個項目中同時使用 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


免責聲明!

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



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