天天看點

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