PostgreSQL Field Guide

JSONB、全文与语义检索

选择关系列、JSONB、全文检索与 pgvector 的边界

PostgreSQL 可以在同一系统里处理关系数据、JSON 文档、词法全文检索,并通过扩展进行向量检索。能放在一起不代表应该把所有问题都塞进一列。

何时使用 JSONB

适合:来源不统一的元数据、低频变化的可选属性、需要保留原始载荷的集成数据。

不适合:主键与外键、金额、权限边界、经常 JOIN/排序/聚合的核心字段。

CREATE TABLE products (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  sku text NOT NULL UNIQUE,
  name text NOT NULL,
  attributes jsonb NOT NULL DEFAULT '{}'::jsonb,
  CHECK (jsonb_typeof(attributes) = 'object')
);

INSERT INTO products (sku, name, attributes)
VALUES ('KB-01', 'Keyboard', '{"layout":"75%","wireless":true}');

SELECT id, name
FROM products
WHERE attributes @> '{"wireless":true}';

JSONB 索引

CREATE INDEX products_attributes_gin
ON products USING gin (attributes);

默认 GIN operator class 支持多种键与包含查询。若工作负载几乎只有 @>jsonb_path_ops 通常索引更小,但支持的运算符集合更窄。用真实查询和数据分布比较。

频繁查询的单个属性可以使用表达式索引,或提升为普通列:

CREATE INDEX products_layout_idx
ON products ((attributes ->> 'layout'));

内置全文检索

ALTER TABLE products ADD COLUMN search_document tsvector
  GENERATED ALWAYS AS (
    setweight(to_tsvector('simple', coalesce(name, '')), 'A') ||
    setweight(to_tsvector('simple', coalesce(attributes::text, '')), 'B')
  ) STORED;

CREATE INDEX products_search_gin
ON products USING gin (search_document);

SELECT id, name,
       ts_rank(search_document, websearch_to_tsquery('simple', $1)) AS rank
FROM products
WHERE search_document @@ websearch_to_tsquery('simple', $1)
ORDER BY rank DESC
LIMIT 20;

中文分词不由内置 simple 配置完整解决;生产中文搜索需要评估专用分词扩展、应用侧分词或外部搜索系统。

语义检索与 pgvector

向量不是 PostgreSQL 核心内置类型。常见方案是安装独立的 pgvector 扩展:

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE document_chunks (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  document_id bigint NOT NULL,
  content text NOT NULL,
  embedding vector(1536) NOT NULL,
  embedding_model text NOT NULL
);

维度必须匹配模型;切换 embedding 模型时不要在同一索引里混用不可比较的向量。保存模型名、切块版本和源文档定位,才能重建与审计。

混合检索通常更稳

用关键词/权限/时间等结构化条件缩小候选集,再做向量相似度排序。始终在 SQL 中执行租户和访问控制过滤,不要依赖模型自行遵守。

需要可运行环境时先阅读安装 pgvector;准备建立近似索引前阅读pgvector 生产最佳实践

Last updated on

On this page