結論先講

如果你的資料在 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_rankts_rank_cd

限制:

  • 中文需要額外的分詞擴展(pg_jiebazhparser
  • 沒有內建的容錯搜尋(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 真正強的地方

  1. 日誌分析(ELK Stack):Elasticsearch + Logstash + Kibana,業界標準
  2. 複雜聚合:跨多個維度的統計、直方圖、嵌套聚合
  3. 大規模資料:水平擴展,處理 TB 級資料
  4. 即時分析:近即時(~1 秒)的索引刷新
  5. 地理搜尋:内建 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:latest
import { 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 分詞,不需外掛
簡單 APIRESTful,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)MeilisearchElasticsearchTypesenseAlgolia (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 起(有免費額度)
Highlightts_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 需要為每個語言建不同的欄位,比較麻煩。


本系列文章

  1. 資料庫全景圖:一張表看懂所有類型
  2. MySQL:為什麼越來越多人覺得它不夠好?
  3. PostgreSQL:為什麼它變成了預設選擇
  4. 快取與 Session 管理:Redis、Memcached、還是直接用 DB?
  5. 全文搜尋:Elasticsearch、Meilisearch、還是 PostgreSQL 就夠了?(本篇)
  6. NoSQL 什麼時候該用
  7. 資料庫設計:正規化與索引策略
  8. SQL 效能調校
  9. 資料庫遷移實戰
  10. 擴展策略:讀寫分離與分片
  11. Docker 環境的資料庫管理
  12. 資料庫監控與告警