PostgreSQL中文全文檢索:pg_trgm 與 pg_bigm 模組性能對比

PostgreSQL中文全文檢索:pg_trgm 與 pg_bigm 模組性能對比

本文研究了 PostgreSQL 的兩個 extension module - pg_trgm 和 pg_bigm,比較它們在中文全文檢索方面的性能和特點。並且有詳細的功能對比和實際的效能測試

為什麼要比較 pg_trgmpg_bigm

主要是因為現在在使用的是 postgreSQL,原本做中文檢索只用 like 很慢,想要做中文全文檢索但是又不想要導入全新工具,受限於中文全文檢索網路上的資源很少,這邊先看看 postgreSQL 有沒有什麼神奇小道具可以支援,而 pg_trgm 和 pg_bigm 這兩個都是 postgres 的 extension,而且 AWS RDS 有支援。

pg_trgm 和 pg_bigm 比較

什麼是 pg_trgm?

pg_trgm module 提供了基於三元組( trigram ) 匹配來確定英文字母數字文本 ( alphanumeric text )相似度的函數和操作符,以及支援快速搜索相似 string index 操作類別

什麼是 pg_bigm?

pg_bigm 是在 pg_trgm 的基礎上開發的,但是他是 2-gram,他也可以支援快速搜索相似 string index

比較表:

功能和特性 pg_trgm pg_bigm
全文搜索的短語匹配方法 3-gram 2-gram
支持的索引類型 GIN 和 GIST GIN
可用的文字搜尋運算符 LIKE、ILIKE、~、~* LIKE
非字母语言的全文本搜索 不支持 支持
帶有1~2个字符的關鍵字的全文本搜索
相似性搜索 支持 支持
最大可以索引的列大小 238,609,291 Bytes(约228 MB) 107,374,180 Bytes(约102 MB)

常見問題

pg_trgm 是否真的有支持中文搜尋?

  • pg_trgm 官方頁面沒有解釋,如果參考官方說明,只有提到 provides functions and operators for determining the similarity of alphanumeric text based on trigram matching 提供匹配英文字母數字的搜尋
  • 根據 pg_bigm 有自製比較表,是說 pg_trgm 不支援非字母语言的全文本搜索 (pg_bigm 連結),參考說明
💡
You can use full text search for non-alphabetic language by commenting out KEEPONLYALNUM macro variable in contrib/pg_trgm/pg_trgm.h and rebuilding pg_trgm module. But pg_bigm provides faster non-alphabetic search than such a modified pg_trgm.
    • 意思是 default 沒有支援,但提到如果改 pg_trgm 底層文件則可以支援
    • pgroonga(另一個 postgeSQL extention) 的比較表有提到一樣的事情 (pgroonga 連結),參考以下
💡
The pg_trgm module implements not language specific full text search. But pg_trgm disables non ASCII characters support. It means that pg_trgm doesn't support many Asian languages such as Japanese and Chinese by default. You need to comment out the following line in contrib/pg_trgm/trgm.h to enable non ASCII characters support
    • 但是改底層的邏輯會有風險,不確定 postgres 升級的時候會不會有什麼漏洞? RDS 不曉得有沒有設定檔可以改?考量到維護性問題,這個做法不實作

雖然上面看了那麼多說明,但網路上還是有人說 pg_trgm 支援中文全文檢索,而且沒有提到他改了底層邏輯,所以下面會直接做測試看看實際狀況。

extension 安裝說明

pg_trgm install

  • AWS RDS 安裝
CREATE EXTENSION pg_trgm;
  • local 端安裝,一樣在資料庫裡建立 extension 就好
CREATE EXTENSION pg_trgm;

pg_bigm install

  • AWS RDS 安裝
    • 已經確認官方文件支援 pg_bigm,這即是說輸入以下 SQL 指令即可安裝 pg_bigm
CREATE EXTENSION pg_bigm;
  • local 端安裝
    • 必須去官方文件下載檔案並安裝,他沒有在 postgreSQL 預設的 extentsion 裡,可以參考官方網站
    • 也可以直接在 docker hub 找找相關 image,先試用

效能測試比較

以下 pg_trgm, pg_bigm 索引類型都是使用 GIN

pg_trgm 測試

測試環境:專案中的 user table,查詢 user name

pg_trgm extension 測試中文查詢

user table name 有下 index,使用 pg_trgm extension

t.index ["name"], name: "index_users_on_name_gin_trgm", opclass: :gin_trgm_ops, using: :gin

⭐ 使用不同文字搜尋運算符測試

搜尋方式 搜尋 scan 狀況 執行時間 使用 index 狀況
~* '陳' Parallel Seq Scan 935.092 ms
~* '陳小' Parallel Seq Scan 944.365 ms
~* '陳小明' Parallel Seq Scan 1084.131 ms
ILIKE '%陳小明%' Bitmap Heap Scan 0.096 ms 使用 index
LIKE '%陳小明%' Bitmap Heap Scan 1.945 ms 使用 index

簡單試過了 pg_trgm 發現實際上是可以進行中文全文檢索的,只是有限制。下面會用同樣測試環境對 pg_trgm 和 pg_bigm 的查詢結果做比較。

測試環境

8400 筆中文資料,每筆資料 200 字繁體中文亂碼,資料源:https://textgen.cqd.tw/

pg_trgm 測試結果整理

  • 當 1 - 2 個英文字時,不使用 index
  • 搜尋 3 個英文字以上使用 index
  • 搜尋 3 個以上英文字開始使用 index
  • 可支援 LIKE 跟 ILIKE
  • 正則表達式查詢中文不會套用 index
  • 查詢 3 個以上中文字以上使用 index
    • 為什麼 pg_trgm 要搜尋三個以上的中文字才有 index?
      • 猜測因為是使用 3-gram,他的搜索原理基於將詞彙切割成一系列的 3 個字符序列來進行匹配,但用 show_trgm 看不出個所以然,看起來已經轉成某種自訂義編碼
SELECT show_trgm('小明')

=> "{0x0419a5,0x8fb472,0xda12d5}"
搜尋方式 搜尋 scan 狀況 使用 trgm 時間 使用 index 狀況
LIKE ‘%v%’ Seq Scan 22.840 ms
LIKE ‘%vi%’ Seq Scan 13.636 ms
LIKE ‘%vic%’ Bitmap Index Scan 2.100 ms index
LIKE ‘%vick%’ Bitmap Index Scan 0.877 ms index
LIKE '%sharon%' Bitmap Index Scan 1.507 ms index
LIKE ‘%為%’ Seq Scan 34.850 ms
LIKE %為了%’ Seq Scan 15.011 ms
LIKE %我為了%’ Bitmap Index Scan 2.498 ms index
LIKE %怎麼會呢% Bitmap Index Scan 1.640 ms index
ILIKE %v%’ Seq Scan 47.042 ms
ILIKE %vi%' Seq Scan 54.985 ms
ILIKE %vic%' Bitmap Index Scan 0.822 ms index
ILIKE %vick%' Bitmap Index Scan 0.662 ms index
ILIKE %tommy%' Bitmap Index Scan 1.665 ms index

pg_bigm 測試結果整理

  • 搜尋英文不管幾個字都使用 index
  • 當搜尋單個中文字,有可能發生沒有使用 index 的狀況,是有查到是常見中文字才有 index,但什麼叫做 「常見」,還沒有找到相關資料
  • 搜尋速度明顯比 pg_trgm 快
  • 使用 LOWER LIKE 皆不使用 index
  • 僅支援 LIKE
  • 用 show_bigm 可以看到明確的中文字切分,show_bigm 傳回的 2-gram 是取自字串的一組兩個連續字符,其中空白字符已附加到開頭和結尾
SELECT show_bigm('小明');

=> {" 小",小明,"明 "}
搜尋方式 搜尋 scan 狀況 使用 bigm 時間 使用 index 狀況
LIKE %m%' Bitmap Index Scan 0.059 ms index
LIKE %ma%’ Bitmap Index Scan 0.061 ms index
LIKE %mar%’ Bitmap Index Scan 0.061 ms index
LIKE %mary%’ Bitmap Index Scan 0.045 ms index
LIKE %天% Seq Scan 16.518 ms
LIKE %肥% Bitmap Index Scan 0.060 ms index
LIKE %肥羊%' Bitmap Index Scan 0.045 ms index
LIKE %台積電% Bitmap Index Scan 0.093 ms index
LOWER LIKE '%v%' Seq Scan 47.362 ms
LOWER LIKE '%vi%' Seq Scan 41.919 ms
LOWER LIKE '%vic%' Seq Scan 47.605 ms
LOWER LIKE ‘%歡樂人%' Seq Scan 48.158 ms

結論

如果單就數量小、簡單的文章要做中文全文檢索,pg_bigm 算堪用,速度上也比較快,pg_trgm 則是意外發現他也可以做全文檢索,只是要 3 個中文字以上,果然網路上查到的資訊都還是需要驗證。但如果要做複雜的全文檢索,還是用其他工具(例如 Elasticsearch)比較適合。

額外再提一下另一個 postgeSQL extension: pgroonga,老實說相對於 pg_bigm 我比較喜歡這個,他目前頻繁在更新,卡就卡在目前 RDS 還沒有支援他,難過,所以只好先不列入上面比較。