見招拆招 PostgreSQL中文全文索引效率優化

2021-09-07 16:24:14 字數 2950 閱讀 8631

上文 使用postgresql進行中文全文檢索 中我使用 postgresql 搭建完成了一套中文全文檢索系統,對資料庫配置和分詞都進行了優化,基本的查詢完全可以支援,但是在使用過程中還是發現了一些很惱人的問題,包括查詢效果和查詢效率,萬幸都一一解決掉了。

其中過程自認為還是很有借鑑意義的,今天來總結分享一下。

一開始是分詞效果的問題:

scws 支援更為靈活的分詞等級,為了能分出較多的詞來盡量包含目標結果,我們將 scws 的分詞等級調為了7(不了解的可以看上文),但同時也引入了更奇葩的問題: 搜尋天安門查不到天安門廣場。。。

原因也很另人無語:

乙個常識:大家想搜乙個地點時大多會先輸入其名稱前面的部分,基於此考慮,我向表內引入 b樹索引支援字首查詢,配合原來分詞的 gin 索引,解決了此問題。

緊接著又發現了新的問題:

postgresql 的 gin 索引(generalized inverted index 通用倒排索引)儲存的是(key, posting list)對, 這裡的 posting list 是一組出現鍵的行id。如 資料:

行id分詞向量

1測試 分詞

2分詞 結果

則索引的內容就是測試=>1 分詞=>1,2 結果=>2,在我們要查詢分詞向量內包含分詞的資料時就可以快速查詢到第1,2列。

但這種設計也帶來了另乙個問題,當某乙個 key 對應的 posting list 過大時,資料操作會很慢,如我們的資料中地點名帶有飯店的資料就很多,有幾十萬,而我們的需求有一項就是要對查詢結果按照評分一列倒序排序,這麼幾十萬資料,資料庫響應超時會達到 3000 ms。

我們期望的響應時間是 90% 50ms 以內,雖然統計結果顯示,確實 90% 的請求已經符合要求,但另外的 10% 完全不能用也是不可能接受的。

接下來的優化就是針對這些 bad case。

可是超時的畢竟只有很少一部分,快取的命中率堪憂。雖然這一小部分查詢可用了,但是所有查詢語句都會多出一次取快取的操作。

為了能提高快取命中率,我還特意統計了關鍵字各長度的搜尋數量佔比和超時率佔比,發現以下情況:

不僅是命中率問題,快取過期時間和快取更新等更是大坑,基於以上考慮,快取方案徹底被放棄。

查詢時我們先通過位置將使用者定位到區域,根據區域 id 確定要查詢的表,再從對應表內查詢結果。

這個方案的缺點也非常多:

終於靈活考慮了業務需求,引入子查詢提出了一種頗為完美的方案:

子查詢用來實現結果集過濾非常有效,如我們可以在極大頁碼查詢分頁時使用子查詢先過濾掉一大批無用資料。

本例中,我們在子查詢語句中使用 limit 語句限制取的結果集條數,從而大大減小排序壓力,查詢語句類似select id from (select * from table where tsv @@ tsq or name like 'keyword%' limit 10000) as tmp order by score desc

這樣優化過後,查詢語句的最差效能也可以穩定在 170ms 以下了。

接著我又嘗試改變 sql 語句的 where 條件,去除or name like 'keyword%'後, 總條數並沒有太大的變動,結果集由 13w 減小到了 11w, 但 新增 limit 後的效率卻急劇提公升:

sql結果條數

響應時間

新增 limit 後

sql響應時間

where tsv @@ tsq or name like 'keyword%'13w

2400ms

where tsv @@ tsq or name like 'keyword%' limit 10000170ms

where tsv @@ tsq11w

1900ms

where tsv @@ tsq limit 1000025ms

這樣對比起來就很明顯了, 分詞查詢的 gin 索引和字首詞查詢的 b樹索引之間配合並不完美。

想想也是,如果在乙個索引上取 1w 條資料,直接取就行了,而如果在兩個索引上取 1w 資料,那麼還得考慮每個索引上各取多少,取完後還要排重。

問題分析完,那麼就得根據問題尋找解決方案了,怎麼能把兩個索引並到同一索引上呢?把分詞 gin 索引並到 b樹索引顯然是不可能的,只能試著使用分詞來替代 b樹索引。

當時有三種方案:

最好的方案當然是最後一種,改動最小,於是我就查詢了一下 postgresql 向量拼接,還是找到了向量拼接的方法,使用::tsvector將字串強轉成向量,再使用||拼接到原來的分詞向量上,sql 語句類似select to_tsvector('parser', 'keyword') || 'prefix'::tsvector

在查詢時,就可以直接使用where tsv @@ to_tsquery('parser', 'keyword')查詢字首了。這樣,子查詢語句的響應時間就可以大大降低了,在 50ms 左右,而且還可以通過減小 limit 值來加快響應。

此後,b樹索引就可以退休啦~

順便吐槽幾句周邊同事對 postgresql 的態度,理由竟然是認為它是乙個開源產品,可能會有各種埋得深的坑,所以不信任。

比較想不到比較前沿的網際網路公司也會有人對開源抱如此看法,不可否認很多開源產品或工具都有各種各樣的坑,但為此因噎廢食大可不必,我們一直在用的 linux/git 還是開源產品呢,可有多少人離不開它們?而且閉源產品就不會出現問題麼?也不可否認 postgresql 小眾,但它也有自己的特色,而且近年來它的占有率一率攀公升,未來什麼樣,還未可知。

支援一下我,部落格一直在更新,歡迎關注

12306多種手段反擊瀏覽器廠商「見招拆招」

多名業內人士證實,從1月中旬開始,12306.cn後台開始了頻繁的公升級。最初的一些改動比較常規,是為了改善使用者體驗。但之後的目的越來越明顯,就是 搶票外掛程式。倪超說。互有攻防 12306多種手段反擊瀏覽器廠商 見招拆招 戰爭 的確仍在繼續。出於道德理想和商業現實的雙重考量,瀏覽器廠商並沒有放棄...