PostgreSQL中文全文檢索:pg_trgm 與 pg_bigm 模組性能對比
本文研究了 PostgreSQL 的兩個 extension module - pg_trgm 和 pg_bigm,比較它們在中文全文檢索方面的性能和特點。並且有詳細的功能對比和實際的效能測試
為什麼要比較 pg_trgm 和 pg_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 連結),參考說明
- 意思是 default 沒有支援,但提到如果改 pg_trgm 底層文件則可以支援
- pgroonga(另一個 postgeSQL extention) 的比較表有提到一樣的事情 (pgroonga 連結),參考以下
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 看不出個所以然,看起來已經轉成某種自訂義編碼
- 為什麼 pg_trgm 要搜尋三個以上的中文字才有 index?
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 還沒有支援他,難過,所以只好先不列入上面比較。