正在加载,请稍候…

PostgreSQL JSONB 与全文搜索:在众多场景中替代 Elasticsearch

使用 PostgreSQL JSONB 列实现灵活模式,并通过 tsvector 进行内置全文搜索。了解何时使用 PostgreSQL 搜索替代 Elastic

PostgreSQL JSONB 与全文搜索:在众多场景中替代 Elasticsearch

PostgreSQL JSONB 与全文搜索

JSONB 列

-- 创建包含 JSONB 的表
CREATE TABLE products (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name        TEXT NOT NULL,
  metadata    JSONB NOT NULL DEFAULT '{}',
  created_at  TIMESTAMPTZ DEFAULT NOW()
);

-- 为快速 JSONB 查询创建 GIN 索引
CREATE INDEX idx_products_metadata ON products USING GIN(metadata);

-- 插入 JSONB 数据
INSERT INTO products (name, metadata) VALUES
  ('Sony WH-1000XM5', '{"brand": "Sony", "category": "headphones", "color": "black", "features": ["ANC", "30h battery"], "price": 399}'),
  ('Apple AirPods Pro', '{"brand": "Apple", "category": "earbuds", "features": ["ANC", "adaptive transparency"], "price": 249}');

PostgreSQL JSONB 与全文搜索:在众多场景中替代 Elasticsearch 示意图

JSONB 查询

-- 包含操作符 @>
SELECT name, metadata
FROM products
WHERE metadata @> '{"brand": "Sony"}';

-- 字段访问 ->>(返回文本)
SELECT name, metadata->>'brand' AS brand, (metadata->>'price')::numeric AS price
FROM products
WHERE (metadata->>'price')::numeric < 300;

-- 数组包含 ?&(包含所有键)或 ?|(包含任一键)
SELECT name
FROM products
WHERE metadata->'features' ? 'ANC';  -- 具有 ANC 功能

-- 嵌套访问
SELECT metadata->'specs'->>'weight' FROM products;

-- 对 JSONB 进行聚合
SELECT
  metadata->>'brand' AS brand,
  COUNT(*) as product_count,
  AVG((metadata->>'price')::numeric) AS avg_price
FROM products
WHERE metadata ? 'price'
GROUP BY metadata->>'brand';

PostgreSQL JSONB 与全文搜索:在众多场景中替代 Elasticsearch 示意图

JSONB 更新

-- 添加/更新字段
UPDATE products
SET metadata = metadata || '{"in_stock": true}'
WHERE id = '...';

-- 删除字段
UPDATE products
SET metadata = metadata - 'discontinued_field'
WHERE id = '...';

-- 更新嵌套字段
UPDATE products
SET metadata = jsonb_set(metadata, '{specs, weight}', '"250g"')
WHERE id = '...';

-- 追加到数组
UPDATE products
SET metadata = jsonb_set(metadata, '{features}', (metadata->'features') || '["Multipoint"]')
WHERE id = '...';

PostgreSQL JSONB 与全文搜索:在众多场景中替代 Elasticsearch 示意图

全文搜索

-- 添加 tsvector 列用于搜索
ALTER TABLE products
ADD COLUMN search_vector TSVECTOR
GENERATED ALWAYS AS (
  to_tsvector('english',
    coalesce(name, '') || ' ' ||
    coalesce(metadata->>'description', '') || ' ' ||
    coalesce(metadata->>'brand', '') || ' ' ||
    coalesce(metadata->>'category', '')
  )
) STORED;

-- 为快速搜索创建 GIN 索引
CREATE INDEX idx_products_search ON products USING GIN(search_vector);

-- 全文搜索
SELECT name, metadata->>'brand' AS brand,
       ts_rank(search_vector, query) AS relevance
FROM products,
     to_tsquery('english', 'noise & cancelling & headphones') query
WHERE search_vector @@ query
ORDER BY relevance DESC;

-- 面向 Web 的搜索(处理部分单词)
SELECT name FROM products
WHERE search_vector @@ websearch_to_tsquery('english', 'noise cancelling headphones')
ORDER BY ts_rank(search_vector, websearch_to_tsquery('english', 'noise cancelling headphones')) DESC;

高亮搜索结果

SELECT
  name,
  ts_headline(
    'english',
    name || ' ' || coalesce(metadata->>'description', ''),
    query,
    'StartSel=<mark>, StopSel=</mark>, MaxWords=30'
  ) AS highlighted
FROM products,
     websearch_to_tsquery('english', 'wireless headphones') query
WHERE search_vector @@ query
LIMIT 10;

何时 PostgreSQL 搜索足够

使用 PostgreSQL FTS 当:
  - 文档数 < 1000 万
  - 搜索只是众多功能之一
  - 数据已在 PostgreSQL 中
  - 简单的关键词搜索即可满足
  - 团队熟悉 SQL

使用 Elasticsearch 当:
  - 文档数 > 1000 万或需要复杂分析
  - 高级功能:模糊搜索、同义词、大规模自动补全
  - 多个数据源一起索引
  - 实时流式分析
  - 搜索是主要产品功能

对于大多数应用,PostgreSQL 的内置全文搜索已经足够强大,并且消除了 Elasticsearch 的运维复杂性。