結論先講
如果你的資料在 PostgreSQL 裡,而且搜尋需求不複雜(部落格搜尋、產品名搜尋、用戶搜尋),先試 PG 的內建全文搜尋 + pg_trgm。 不夠再上 Meilisearch(簡單、快、容錯)。Elasticsearch 留給真正需要它的場景——日誌分析、複雜聚合、TB 級資料。
很多團隊一開始就上 Elasticsearch,結果花了 80% 的時間在維護 ES 叢集,只用了它 20% 的功能。
PostgreSQL 全文搜尋:被低估的方案
PG 內建兩套搜尋機制,大多數人只知道 LIKE,不知道 tsvector。
tsvector + tsquery(正規全文搜尋)
-- 建立搜尋用的欄位和索引
ALTER TABLE articles ADD COLUMN search_vector tsvector;
UPDATE articles SET search_vector =
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(content, '')), 'B');
CREATE INDEX idx_articles_search ON articles USING gin(search_vector);
-- 搜尋
SELECT title, ts_rank(search_vector, query) AS rank
FROM articles,
to_tsquery('english', 'postgresql & performance') AS query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 10;功能:
- 詞幹化(searching → search)
- 權重(標題比內文重要)
- 布林運算(AND、OR、NOT)
- 片語搜尋(
<->相鄰運算子) - 排名(
ts_rank、ts_rank_cd)
限制:
- 中文需要額外的分詞擴展(
pg_jieba、zhparser) - 沒有內建的容錯搜尋(typo tolerance)
- 沒有即時搜尋(search-as-you-type)的最佳化
- Highlight 功能比較陽春
pg_trgm(模糊搜尋)
tsvector 不能處理拼錯字的情況。pg_trgm 可以:
CREATE EXTENSION pg_trgm;
CREATE INDEX idx_products_name_trgm ON products USING gin (name gin_trgm_ops);
-- 模糊搜尋
SELECT name, similarity(name, 'mechancal keyboard') AS sim
FROM products
WHERE name % 'mechancal keyboard'
ORDER BY sim DESC
LIMIT 10;
-- 拼錯了 mechanical,但還是能找到
-- 也可以用 ILIKE + 索引(pg_trgm 讓 LIKE/ILIKE 也能用索引)
SELECT * FROM products WHERE name ILIKE '%keyboard%';兩者組合
-- tsvector 做主搜尋,pg_trgm 做 fallback
WITH fts_results AS (
SELECT id, title, ts_rank(search_vector, query) AS rank
FROM articles, to_tsquery('english', 'postgrsql') AS query
WHERE search_vector @@ query
),
fuzzy_results AS (
SELECT id, title, similarity(title, 'postgrsql') AS rank
FROM articles
WHERE title % 'postgrsql'
AND id NOT IN (SELECT id FROM fts_results)
)
SELECT * FROM fts_results
UNION ALL
SELECT * FROM fuzzy_results
ORDER BY rank DESC
LIMIT 10;PG 搜尋夠用的場景
- 部落格文章搜尋(< 10 萬篇)
- 產品名稱搜尋(< 100 萬筆)
- 用戶名 / Email 搜尋
- 後台管理系統的搜尋功能
- 資料量不大、搜尋不是核心功能的應用
Elasticsearch:大砲打蚊子還是剛剛好?
Elasticsearch 是搜尋領域的 800 磅大猩猩。功能最全、生態最大、但也最重。
什麼是 Elasticsearch?
基於 Apache Lucene 的分散式搜尋引擎。資料以 JSON 文件存儲,自動建立倒排索引。
// 建立索引
await client.indices.create({
index: 'articles',
body: {
mappings: {
properties: {
title: {
type: 'text',
analyzer: 'ik_max_word', // 中文分詞
fields: {
keyword: { type: 'keyword' } // 精確匹配用
}
},
content: { type: 'text', analyzer: 'ik_smart' },
tags: { type: 'keyword' },
published_at: { type: 'date' },
view_count: { type: 'integer' }
}
}
}
});
// 複雜搜尋 + 聚合
const result = await client.search({
index: 'articles',
body: {
query: {
bool: {
must: [
{ multi_match: {
query: 'PostgreSQL 效能',
fields: ['title^3', 'content'], // 標題權重 x3
fuzziness: 'AUTO'
}},
],
filter: [
{ range: { published_at: { gte: '2025-01-01' } } },
{ terms: { tags: ['database', 'postgresql'] } }
]
}
},
highlight: {
fields: { content: { fragment_size: 150 } }
},
aggs: {
by_tag: { terms: { field: 'tags', size: 10 } },
by_month: { date_histogram: { field: 'published_at', calendar_interval: 'month' } }
}
}
});Elasticsearch 真正強的地方
- 日誌分析(ELK Stack):Elasticsearch + Logstash + Kibana,業界標準
- 複雜聚合:跨多個維度的統計、直方圖、嵌套聚合
- 大規模資料:水平擴展,處理 TB 級資料
- 即時分析:近即時(~1 秒)的索引刷新
- 地理搜尋:内建 geo_point、geo_shape
Elasticsearch 的痛
| 痛點 | 說明 |
|---|---|
| 資源消耗 | 最少建議 4GB RAM 給 JVM,生產環境 16GB+ |
| 運維複雜 | 叢集管理、分片規劃、mapping 衝突、版本升級 |
| 不是資料庫 | 不保證寫入一致性,不能當主資料來源 |
| 學習曲線 | Query DSL 很強但也很複雜 |
| 授權問題 | 7.11 後改 SSPL,催生了 OpenSearch fork |
Meilisearch:開發者友善的選擇
2026 年最值得關注的搜尋引擎。Rust 寫的,速度快、設定簡單、內建容錯。
# 啟動(一行搞定)
docker run -p 7700:7700 getmeili/meilisearch:latestimport { MeiliSearch } from 'meilisearch';
const client = new MeiliSearch({ host: 'http://localhost:7700', apiKey: 'masterKey' });
// 新增文件
await client.index('products').addDocuments([
{ id: 1, name: '機械鍵盤 Cherry MX Brown', category: '周邊', price: 3500 },
{ id: 2, name: '機械鍵盤 Cherry MX Red', category: '周邊', price: 3200 },
{ id: 3, name: '靜電容鍵盤 Topre', category: '周邊', price: 8500 },
]);
// 搜尋——就這麼簡單
const results = await client.index('products').search('機械建盤', {
// 打錯字(建→鍵)也能找到!
filter: ['price < 5000'],
sort: ['price:asc'],
limit: 10,
});Meilisearch 的優勢
| 特點 | 說明 |
|---|---|
| 容錯搜尋 | 內建 typo tolerance,不需設定 |
| 即時搜尋 | 50ms 內回應,搜尋即時出結果 |
| 中文支援 | 內建 CJK 分詞,不需外掛 |
| 簡單 API | RESTful,10 分鐘上手 |
| 排序 & 過濾 | 內建,語法直覺 |
| 資源需求低 | 單機就能跑,128MB RAM 起跳 |
Meilisearch 的限制
- 資料量上限:實務上百萬級是上限(不像 ES 可以 TB 級)
- 沒有複雜聚合(Aggregations)
- 叢集模式還在發展中
- 不適合日誌分析場景
Typesense:另一個輕量選項
和 Meilisearch 定位類似,C++ 寫的,效能稍好:
const typesense = require('typesense');
const client = new typesense.Client({
nodes: [{ host: 'localhost', port: '8108', protocol: 'http' }],
apiKey: 'xyz',
});
// 建立 collection(需要先定義 schema)
await client.collections().create({
name: 'products',
fields: [
{ name: 'name', type: 'string' },
{ name: 'price', type: 'int32', facet: true },
{ name: 'category', type: 'string', facet: true },
],
default_sorting_field: 'price',
});
// 搜尋
const results = await client.collections('products').documents().search({
q: '機械鍵盤',
query_by: 'name',
filter_by: 'price:<5000',
sort_by: 'price:asc',
});Typesense vs Meilisearch: 功能差不多。Typesense 需要先定義 schema(比較嚴格),Meilisearch 不用(比較彈性)。效能在大部分場景差異不大。選你喜歡的 API 風格。
全方位比較表
| 面向 | PostgreSQL (tsvector + pg_trgm) | Meilisearch | Elasticsearch | Typesense | Algolia (SaaS) |
|---|---|---|---|---|---|
| 設定複雜度 | 低(已有 PG 就行) | 低 | 高 | 中 | 低(但貴) |
| 容錯搜尋 | pg_trgm(手動) | 內建 | fuzziness 參數 | 內建 | 內建 |
| 中文支援 | 需擴展(zhparser) | 內建 | 需 ik plugin | 內建 | 內建 |
| 即時搜尋 | 較慢 | 極快(< 50ms) | 快 | 極快 | 極快 |
| 複雜聚合 | SQL GROUP BY | 有限 | 極強 | 有限 | 有限 |
| 資料規模 | 百萬級 | 百萬級 | TB 級 | 百萬級 | 百萬級 |
| 資源需求 | 無(共用 PG) | 低(128MB+) | 高(4GB+) | 低(256MB+) | 無(SaaS) |
| 日誌分析 | 不適合 | 不適合 | 最佳 | 不適合 | 不適合 |
| 地理搜尋 | PostGIS | 內建 | 內建 | 內建 | 內建 |
| 開源 | 是 | 是 | SSPL / OpenSearch | 是(GPL-3) | 否 |
| 月費(自架) | $0 | $5-20 | $50-200+ | $5-20 | $0 起(有免費額度) |
| Highlight | ts_headline(基本) | 內建 | 內建(強) | 內建 | 內建 |
決策矩陣:你到底該用哪個?
你的資料在哪裡?
├── PostgreSQL → 搜尋需求複雜嗎?
│ ├── 不複雜(關鍵字搜尋、模糊搜尋)→ PG 內建 tsvector + pg_trgm
│ ├── 需要容錯 + 即時搜尋 → Meilisearch
│ └── 需要日誌分析 / 複雜聚合 → Elasticsearch
├── MongoDB / 其他 → 需要獨立搜尋服務
│ ├── 開發者體驗優先 → Meilisearch
│ ├── 大規模 + 複雜需求 → Elasticsearch
│ └── 不想自架 → Algolia
└── 還沒決定 → 先用 PG,搜尋不夠再加
實戰範例:部落格搜尋
假設你有一個部落格,10 萬篇文章,需要支援標題和內容搜尋。
方案 A:PostgreSQL
-- 建立搜尋索引(一次性)
ALTER TABLE posts ADD COLUMN search_vector tsvector;
CREATE OR REPLACE FUNCTION update_search_vector()
RETURNS trigger AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('english', coalesce(NEW.title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(NEW.content, '')), 'B');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER posts_search_update
BEFORE INSERT OR UPDATE ON posts
FOR EACH ROW EXECUTE FUNCTION update_search_vector();
CREATE INDEX idx_posts_search ON posts USING gin(search_vector);
-- 搜尋 API
SELECT id, title,
ts_headline('english', content, query, 'MaxFragments=2') AS snippet,
ts_rank(search_vector, query) AS rank
FROM posts,
plainto_tsquery('english', $1) AS query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 20;效能: 10 萬篇文章,搜尋大約 5-20ms。夠了。
方案 B:Meilisearch
// 同步資料到 Meilisearch
const posts = await db.query('SELECT id, title, content, tags FROM posts');
await meiliClient.index('posts').addDocuments(posts);
// 設定可搜尋欄位和權重
await meiliClient.index('posts').updateSettings({
searchableAttributes: ['title', 'content', 'tags'],
rankingRules: ['words', 'typo', 'proximity', 'attribute', 'sort', 'exactness'],
filterableAttributes: ['tags'],
});
// 前端即時搜尋
const results = await meiliClient.index('posts').search(userInput, {
limit: 20,
attributesToHighlight: ['title', 'content'],
highlightPreTag: '<mark>',
highlightPostTag: '</mark>',
});效能: 10 萬篇文章,搜尋 < 10ms,內建容錯和 highlight。
選哪個?
| 考量 | PG 方案 | Meilisearch 方案 |
|---|---|---|
| 維運成本 | 零(已有 PG) | 多一個服務 |
| 搜尋體驗 | 基本 | 更好(容錯、highlight) |
| 資料同步 | 不需要 | 需要同步機制 |
| 前端體驗 | 需自己做 | 有現成 UI 套件 |
我的建議: 先用 PG 方案上線。如果用戶抱怨搜尋體驗,再加 Meilisearch。不要一開始就 over-engineer。
Algolia:SaaS 選項
如果你完全不想管搜尋基礎設施:
- 優點:極快、全球 CDN、有現成的前端元件(InstantSearch)
- 缺點:貴。免費額度只有 10K 搜尋/月,超過就要付費
- 適合:電商網站、SaaS 產品、有預算的團隊
- 不適合:個人部落格、小型專案、需要資料落地的場景
常見問題
PostgreSQL 的全文搜尋效能夠嗎?
對大多數應用(< 100 萬筆資料)來說,足夠了。10 萬篇文章的搜尋通常在 10-20ms 內完成。超過百萬筆或需要複雜分詞時,再考慮專用搜尋引擎。
Meilisearch 和 Elasticsearch 差在哪?
定位不同。Meilisearch 是「開箱即用的前端搜尋」,Elasticsearch 是「可以做任何事的搜尋與分析引擎」。如果你不需要日誌分析、複雜聚合、TB 級資料,Meilisearch 更適合。
Elasticsearch 的 SSPL 授權有什麼影響?
如果你是自架使用,沒有影響。SSPL 主要限制的是「把 Elasticsearch 當服務賣」(像 AWS 做的那樣)。如果你擔心,可以用 OpenSearch(AWS 的 fork,Apache 2.0 授權)。
中文搜尋用哪個方案最好?
Meilisearch 和 Typesense 都內建 CJK 分詞,開箱即用。Elasticsearch 需要安裝 ik 或 jieba 分詞器。PostgreSQL 需要 zhparser 或 pg_jieba 擴展。論方便程度:Meilisearch > Typesense > Elasticsearch > PostgreSQL。
需要同時搜尋多種語言怎麼辦?
Elasticsearch 最擅長這個——你可以為每個語言設定不同的 analyzer。Meilisearch 自動偵測語言,大部分情況也能處理。PG 的 tsvector 需要為每個語言建不同的欄位,比較麻煩。
本系列文章
- 資料庫全景圖:一張表看懂所有類型
- MySQL:為什麼越來越多人覺得它不夠好?
- PostgreSQL:為什麼它變成了預設選擇
- 快取與 Session 管理:Redis、Memcached、還是直接用 DB?
- 全文搜尋:Elasticsearch、Meilisearch、還是 PostgreSQL 就夠了?(本篇)
- NoSQL 什麼時候該用
- 資料庫設計:正規化與索引策略
- SQL 效能調校
- 資料庫遷移實戰
- 擴展策略:讀寫分離與分片
- Docker 環境的資料庫管理
- 資料庫監控與告警