引言:為什么不用LIKE?
當我們需要在文章、帖子或產品描述中搜索關鍵詞時,第一個蹦進腦海的可能是SQL的LIKE
或ILIKE
操作符:
SELECT * FROM posts WHERE body LIKE '%postgres%';
這種方式簡單但問題很多:
性能極差:%postgres%
這種前導通配符無法利用索引,會導致全表掃描。
不夠智能:它只能進行嚴格的字符匹配。搜索"run"
,不會找到"running"
或"ran"
。
沒有相關性排序:它只能告訴你“有”或“沒有”,無法告訴你哪條結果更相關。
而PostgreSQL內置的全文搜索(Full-Text Search, FTS) 就是為了解決這些問題而生的。
一、核心概念:文檔、向量與查詢
PostgreSQL的FTS將搜索過程抽象為三步:
文檔(Document):待搜索的文本單元。可以是一篇文章、一條評論,或者由多個字段組合而成的文本(如 title || ' ' || body
)。
向量(tsvector):對文檔進行處理后的產物。它:
解析和分詞:將文本拆分成一個個獨立的 token(詞位)。
標準化:轉換為小寫。
去除停用詞:移除a
, the
, is
等常見但無搜索意義的詞。
提取詞干:將單詞變為其詞根形式(如 running
-> run
)。
最終存儲的是(詞位, 位置)
形式的列表。
查詢(tsquery):代表用戶要搜索的內容。它也可以進行詞干提取,并支持邏輯操作符(&
與, |
或, !
非)。
搜索的本質,就是判斷 @@
操作符兩邊的 tsvector
和 tsquery
是否匹配。
二、逐行詳解示例SQL
拆解以下SQL語句:
SELECT title, body
FROM posts
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'postgres & optimization');
to_tsvector('english', body)
功能:生成一個 tsvector
。
參數1:'english'
:這是一個文本搜索配置(Text Search Configuration)。它決定了使用哪種語言的規則進行分詞、停用詞處理和詞干提取。PostgreSQL支持多種語言(如simple
, english
, spanish
, russian
等)。配置的選擇直接影響搜索結果。
參數2:body
:需要被處理的文本字段。
結果示例:假設body
字段內容是 "PostgreSQL provides powerful optimization tools."
,經過處理后會變成: 'power':5 'optim':6 'postgresql':1 'provid':2 'tool':7
。可以看到,provides
變成了provid
(詞干提取),the
被移除(停用詞)。
to_tsquery('english', 'postgres & optimization')
功能:生成一個 tsquery
。
參數1:'english'
:同樣需要指定配置,確保查詢詞和處理文檔時使用相同的規則(例如,optimization
也會被提取詞干為optim
)。
參數2:'postgres & optimization'
:查詢字符串。&
表示同時包含這兩個詞。
結果:一個代表包含詞干 'postgres' 且 包含詞干 'optim'
的查詢對象。
@@
操作符
三、超越基礎:排名與高亮
僅僅找到匹配的文檔還不夠,我們通常需要最好的結果排在前面。
相關性排名(Ranking): 使用ts_rank
函數。
SELECT title, body,
ts_rank(to_tsvector('english', body),
to_tsquery('english', 'postgres & optimization')) AS rank
FROM posts
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'postgres & optimization')
ORDER BY rank DESC;
ts_rank
會根據詞位出現的頻率、位置等因素計算一個相關性分數,然后通過ORDER BY rank DESC
將最相關的結果排在頂部。
結果高亮(Highlighting): 使用ts_headline
函數。
SELECT title,
ts_headline('english', body,
to_tsquery('english', 'postgres & optimization')) AS headline
FROM posts
WHERE ...;
ts_headline
會在結果中環繞匹配到的詞加上諸如<b>...</b>
這樣的HTML標簽,讓用戶在上下文中一眼就看到為什么這條結果被匹配上了,體驗堪比專業搜索引擎。
四、性能優化:GiST/GIN 索引
在WHERE
子句中使用函數調用(如to_tsvector(...)
)通常會導致無法使用索引。為了讓全文搜索飛起來,必須創建專門的索引。
首選GIN索引,它專為這種“多值列”(一個tsvector
里包含很多個詞位)的查詢而優化。
ALTER TABLE posts
ADD COLUMN body_tsvector tsvector
GENERATED ALWAYS AS (to_tsvector('english', COALESCE(body, ''))) STORED;
CREATE INDEX idx_posts_body_fts ON posts USING GIN(body_tsvector);
SELECT title, body
FROM posts
WHERE body_tsvector @@ to_tsquery('english', 'postgres & optimization');
五、與Elasticsearch的比較
特性 | PostgreSQL FTS | Elasticsearch |
---|
部署復雜度 | 零成本,內置,無需額外部署 | 需要單獨部署和維護另一個分布式系統 |
數據一致性 | 強一致,搜索和事務在同一數據庫,無延遲 | 最終一致,數據同步有延遲(取決于刷新間隔) |
功能豐富度 | 支持基礎到中級的需求(分詞、排名、高亮) | 極其豐富,專業的分布式搜索引擎,支持聚合分析、同義詞、拼音搜索等 |
性能與擴展性 | 單機性能優秀,可通過分片擴展 | 為大規模分布式搜索和水平擴展而生 |
結論:如何選擇?
總結
PostgreSQL的全文搜索是一個被嚴重低估的“隱藏寶石”。它提供了一個在** simplicity(簡單性)** 和 power(功能) 之間絕佳平衡的解決方案。對于許多不需要Elasticsearch這種“重武器”的應用場景來說,它完全足夠且是更優雅的選擇。
通過本文的介紹,希望你能在你的下一個項目中輕松上手這項強大功能!
討論點:
大家在項目中是用PG的全文搜索還是ES?為什么做出這個選擇?
在處理中文全文搜索時,有什么好的配置或分詞方案推薦嗎?(這是一個常見痛點,可以引出zhparser
等擴展的討論)