pgvector 向量搜尋:從 Embedding 到 RAG 的完整實戰 | PostgreSQL

2026/07/19
pgvector 向量搜尋:從 Embedding 到 RAG 的完整實戰 | PostgreSQL

pgvectorPostgreSQL 最受歡迎的向量搜尋擴展,讓你可以在關聯式資料庫中直接儲存、索引並查詢高維度 Embedding 向量。無論是建構 RAG 知識庫、語意搜尋引擎還是推薦系統,pgvector 都能讓你用熟悉的 SQL 完成所有操作,不需要另外維護一套專用向量資料庫。本文從原理到實戰,帶你掌握 pgvector 的完整使用方式。

為什麼選擇 pgvector

在 AI 應用爆發的時代,「向量搜尋」已經成為基礎建設。Pinecone、Qdrant、Weaviate 等專用向量資料庫各有強項,但對已經使用 PostgreSQL 的團隊來說,pgvector 有著無可取代的優勢:

優勢說明
零額外基礎設施不需要另外部署、維護向量資料庫,一個 PostgreSQL 搞定所有
ACID 事務保證向量資料與業務資料在同一筆事務中更新,確保一致性
SQL 整合向量搜尋結果可以直接 JOIN 關聯式資料表,不需要跨系統查詢
生態系完整LangChain、LlamaIndex、Supabase 等主流框架原生支援
成本低無 SaaS 按用量計費,只有基礎設施成本

向量搜尋原理

向量搜尋的核心概念是將非結構化資料(文字、圖片、音訊)透過 Embedding 模型轉換為高維度的數值向量,再用數學距離函式找出語義最相近的結果。

Embedding 的直觀理解——語義相似的文字在向量空間中的位置也相近:

「貓咪很可愛」→ [0.21, -0.08, 0.74, ..., 0.33]  (1536 維)
「小貓真有趣」→ [0.19, -0.11, 0.71, ..., 0.35]  (1536 維,向量距離近)
「股票市場」  → [-0.55, 0.82, -0.12, ..., 0.01]  (1536 維,向量距離遠)

傳統 SQL 的 WHERE 子句是精確匹配,無法表達「語義相近」這種模糊概念。向量搜尋填補了這個缺口,使「找出意思相近的文件」成為可能。

向量搜尋在 RAG 中的角色:

使用者查詢
    │
    ▼
Embedding 模型(如 OpenAI text-embedding-3-small)
    │
    ▼
查詢向量 [0.21, -0.08, ...]
    │
    ▼
pgvector 相似性搜尋 → 取得 Top-K 相關文件
    │
    ▼
將文件上下文注入 LLM 提示詞
    │
    ▼
LLM 生成最終回答

這個流程就是 RAG(Retrieval-Augmented Generation),目前企業 AI 應用最常見的架構之一。

安裝與啟用

系統套件安裝(推薦)

# Ubuntu / Debian
sudo apt install postgresql-17-pgvector

# macOS(Homebrew)
brew install pgvector

# 確認安裝
psql -c "SELECT * FROM pg_available_extensions WHERE name = 'vector';"

Docker 方式

# docker-compose.yml
services:
  postgres:
    image: pgvector/pgvector:pg17
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

啟用擴展

-- 在目標資料庫中啟用
CREATE EXTENSION IF NOT EXISTS vector;

-- 確認版本
SELECT extversion FROM pg_extension WHERE extname = 'vector';
-- 結果:0.8.0

雲端託管支援

平台支援方式
Supabase預設啟用,無需額外設定
NeonCREATE EXTENSION vector 啟用
AWS RDS (PG 15+)透過 Parameter Group 啟用
Azure Flexible Server原生支援
Google Cloud SQL (PG 14+)原生支援

向量型別

pgvector 提供四種向量型別,適用於不同場景:

型別精度每維度大小最大維度適用情境
vectorfloat324 bytes16,000通用,OpenAI/Cohere Embedding
halfvecfloat162 bytes16,000記憶體受限、大規模部署
sparsevecfloat32僅非零值1,000,000+稀疏 Embedding(SPLADE 等)
bit二元1/8 byte無限制二元向量、高速篩選

vector — 標準浮點向量

-- 建立含向量欄位的資料表
CREATE TABLE documents (
    id          BIGSERIAL PRIMARY KEY,
    content     TEXT NOT NULL,
    metadata    JSONB,
    embedding   vector(1536)  -- OpenAI text-embedding-3-small 的維度
);

-- 插入向量資料
INSERT INTO documents (content, embedding)
VALUES (
    '機器學習是人工智慧的子領域',
    '[0.021, -0.043, 0.187, ..., 0.034]'::vector
);

-- 基本相似性查詢(餘弦距離,取最近 5 筆)
SELECT id, content,
       embedding <=> '[0.019, -0.041, 0.191, ...]'::vector AS distance
FROM documents
ORDER BY distance
LIMIT 5;

halfvec — 半精度向量(0.7.0+)

-- 使用 16 位元半精度浮點數,儲存空間減少 50%
CREATE TABLE documents_half (
    id        BIGSERIAL PRIMARY KEY,
    content   TEXT,
    embedding halfvec(3072)  -- text-embedding-3-large 的維度
);

-- 查詢語法相同
SELECT content, embedding <=> '[0.019, ...]'::halfvec AS distance
FROM documents_half
ORDER BY distance
LIMIT 10;

sparsevec — 稀疏向量(0.7.0+)

-- 只儲存非零元素,適合 BM25/SPLADE 等稀疏表示
CREATE TABLE sparse_docs (
    id        BIGSERIAL PRIMARY KEY,
    content   TEXT,
    embedding sparsevec(100000)
);

-- 稀疏向量表示格式:{index:value, ...}/dimensions
INSERT INTO sparse_docs (content, embedding)
VALUES (
    'PostgreSQL 向量搜尋',
    '{1:0.8, 42:0.3, 1337:0.5}/100000'::sparsevec
);

距離函式

pgvector 支援三種主要距離度量,每種都有對應的運算子:

距離函式運算子公式適用情境
L2 距離(歐氏距離)<->√(Σ(aᵢ - bᵢ)²)向量已正規化、圖像特徵比對
內積(Dot Product)<#>-Σ(aᵢ × bᵢ)已正規化 Embedding、推薦系統
餘弦距離<=>1 - (a·b)/(‖a‖‖b‖)文字 Embedding、語義相似度
-- 餘弦距離(最常用於文字 Embedding)
SELECT id, content, embedding <=> query_vec AS cosine_dist
FROM documents ORDER BY cosine_dist LIMIT 5;

-- L2 距離
SELECT id, content, embedding <-> query_vec AS l2_dist
FROM documents ORDER BY l2_dist LIMIT 5;

-- 內積(注意回傳負值,最小值 = 最相似)
SELECT id, content, embedding <#> query_vec AS neg_inner_product
FROM documents ORDER BY neg_inner_product LIMIT 5;

距離函式選擇指引:

情境建議距離函式原因
OpenAI / Cohere Embedding餘弦距離或內積模型輸出已正規化向量
圖像特徵向量L2 距離向量長度具有語義意義
推薦系統內積效能最佳,向量通常已正規化
正規化向量(單位向量)三者等價可優先選內積獲得最佳效能

0.7.0 版本另加入 L1 距離<+>)、漢明距離<~>)、Jaccard 距離<%>)。

索引演算法:IVFFlat vs HNSW

向量搜尋的核心挑戰是「維度詛咒」——在高維空間中,精確最近鄰搜尋的計算量隨維度爆炸性增長。pgvector 提供兩種近似最近鄰(ANN)索引來解決這個問題。

IVFFlat 索引

IVFFlat(Inverted File with Flat Compression)的工作原理:

全量向量空間(N 個向量)
├── Centroid 1 → [向量A, 向量B, 向量C, ...]
├── Centroid 2 → [向量D, 向量E, ...]
├── Centroid 3 → [向量F, 向量G, ...]
│   ...
└── Centroid K → [...]

查詢時只搜尋最近的 probes 個 Centroid 的列表
  1. 訓練期:K-means 將所有向量分群為 lists 個群集
  2. 查詢期:找出最近的 probes 個群集,只在這些群集中精確搜尋
-- 建立 IVFFlat 索引(餘弦距離)
CREATE INDEX ON documents
  USING ivfflat (embedding vector_cosine_ops)
  WITH (lists = 100);

-- 查詢時調整 probes(搜尋更多群集 = 更高 Recall)
SET ivfflat.probes = 10;

關鍵參數:

參數說明建議值
lists分群數量rows / 1000(至少 100)
ivfflat.probes查詢搜尋的群集數√listslists/10

注意:索引建立前需要資料已存在(K-means 需要訓練資料)。

HNSW 索引

HNSW(Hierarchical Navigable Small World)的工作原理:

Layer 2(最稀疏):A ─────────────── H
Layer 1:         A ── C ── E ── G ── H
Layer 0(最密):  A─B─C─D─E─F─G─H─...

查詢從 Layer 2 快速定位大致方向,逐層精細搜尋
-- 建立 HNSW 索引(餘弦距離)
CREATE INDEX ON documents
  USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

-- 查詢時調整 ef_search(更大 = 更高 Recall、更慢)
SET hnsw.ef_search = 100;

關鍵參數:

參數說明預設值調校建議
m每個節點的最大連結數16Recall 要求高則增大(最大 256)
ef_construction建立時的候選列表大小64增大提高 Recall,建立更慢
hnsw.ef_search查詢時的候選列表大小40增大提高 Recall,查詢更慢

兩者比較

指標IVFFlatHNSW
建立速度慢(約 3-5 倍)
查詢速度較慢
記憶體使用高(約 2-3 倍)
Recall@10~95%~98%
動態插入差(需重建索引)佳(無需重建)
適用場景靜態資料集、記憶體受限動態資料、高 Recall 需求

選擇建議:大多數場景推薦 HNSW,除非記憶體嚴重受限或資料幾乎不更新。

確認索引被使用

-- 使用 EXPLAIN 確認查詢計畫
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, content
FROM documents
ORDER BY embedding <=> '[0.021, -0.043, ...]'::vector
LIMIT 10;

-- 若看到 "Index Scan using xxx_idx" → 索引有效
-- 若看到 "Seq Scan" → 可能原因:
--   1. 資料量不足(優化器認為全表掃描更快)
--   2. LIMIT 值太大
--   3. 索引 operator class 與查詢運算子不符

LangChain 整合

LangChain 是最主流的 RAG 框架,與 pgvector 整合非常簡單:

from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import PGVector
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# 設定連線
CONNECTION_STRING = "postgresql+psycopg2://user:password@localhost:5432/mydb"

# 初始化 Embedding 模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# 建立或連接向量資料庫
vectorstore = PGVector(
    collection_name="documents",
    connection_string=CONNECTION_STRING,
    embedding_function=embeddings,
)

# 新增文件
docs = [
    Document(page_content="PostgreSQL 是功能強大的關聯式資料庫", metadata={"source": "wiki"}),
    Document(page_content="pgvector 讓 PostgreSQL 支援向量搜尋", metadata={"source": "docs"}),
    Document(page_content="RAG 架構使用向量搜尋增強 LLM 回答", metadata={"source": "blog"}),
]
vectorstore.add_documents(docs)

# 相似性搜尋
results = vectorstore.similarity_search("PostgreSQL 如何支援 AI 應用?", k=3)

# 建立完整 RAG Chain
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
llm = ChatOpenAI(model="gpt-4o-mini")

prompt = ChatPromptTemplate.from_template("""
根據以下上下文回答問題:

上下文:{context}

問題:{question}
""")

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

answer = rag_chain.invoke("pgvector 有什麼優勢?")
print(answer)

原生 Python 實作 RAG

不依賴框架,直接用 psycopg2 + OpenAI API 實作完整 RAG 流程:

import psycopg2
import psycopg2.extras
import openai

def get_embedding(text: str, model: str = "text-embedding-3-small") -> list:
    """呼叫 OpenAI API 取得 Embedding 向量"""
    client = openai.OpenAI()
    response = client.embeddings.create(input=text, model=model)
    return response.data[0].embedding

def ingest_document(conn, content: str, metadata: dict = None):
    """將文件及其 Embedding 存入 PostgreSQL"""
    embedding = get_embedding(content)
    with conn.cursor() as cur:
        cur.execute(
            """INSERT INTO documents (content, metadata, embedding)
               VALUES (%s, %s, %s::vector) RETURNING id""",
            (content, psycopg2.extras.Json(metadata or {}), embedding)
        )
        doc_id = cur.fetchone()[0]
    conn.commit()
    return doc_id

def semantic_search(conn, query: str, k: int = 5, threshold: float = 0.8):
    """語意搜尋:找出與查詢最相似的文件"""
    query_embedding = get_embedding(query)
    with conn.cursor() as cur:
        cur.execute(
            """SELECT id, content, metadata,
                      1 - (embedding <=> %s::vector) AS similarity
               FROM documents
               WHERE 1 - (embedding <=> %s::vector) > %s
               ORDER BY embedding <=> %s::vector
               LIMIT %s""",
            (query_embedding, query_embedding, threshold, query_embedding, k)
        )
        return [
            {"id": r[0], "content": r[1], "metadata": r[2], "similarity": r[3]}
            for r in cur.fetchall()
        ]

def rag_answer(conn, question: str) -> str:
    """完整 RAG 流程:搜尋 → 組合提示詞 → LLM 回答"""
    # Step 1: 向量搜尋
    relevant_docs = semantic_search(conn, question, k=4)
    context = "\n\n".join(doc["content"] for doc in relevant_docs)

    # Step 2: 呼叫 LLM
    client = openai.OpenAI()
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"根據以下資料回答:\n{context}\n\n問題:{question}"}]
    )
    return response.choices[0].message.content

結合向量搜尋與全文搜尋,使用 Reciprocal Rank Fusion(RRF) 融合排名:

-- 混合搜尋:向量相似度 + BM25 全文搜尋
WITH vector_results AS (
    SELECT id, content,
           ROW_NUMBER() OVER (
               ORDER BY embedding <=> '[...]'::vector
           ) AS vector_rank
    FROM documents
    ORDER BY embedding <=> '[...]'::vector
    LIMIT 50
),
text_results AS (
    SELECT id, content,
           ROW_NUMBER() OVER (
               ORDER BY ts_rank(
                   to_tsvector('english', content),
                   plainto_tsquery('pgvector')
               ) DESC
           ) AS text_rank
    FROM documents
    WHERE to_tsvector('english', content) @@ plainto_tsquery('pgvector')
    LIMIT 50
)
-- RRF 融合排名(k=60 是常用常數)
SELECT
    COALESCE(v.id, t.id) AS id,
    COALESCE(v.content, t.content) AS content,
    COALESCE(1.0 / (60 + v.vector_rank), 0) +
    COALESCE(1.0 / (60 + t.text_rank), 0) AS rrf_score
FROM vector_results v
FULL OUTER JOIN text_results t ON v.id = t.id
ORDER BY rrf_score DESC
LIMIT 10;

混合搜尋的優勢在於:向量搜尋擅長語義理解,全文搜尋擅長精確關鍵字匹配,兩者互補能顯著提升搜尋品質。

百萬級向量效能調校

PostgreSQL 設定調校

-- postgresql.conf 關鍵設定

-- 共享記憶體(建議為 RAM 的 25%)
shared_buffers = '8GB'

-- 工作記憶體(影響排序、Hash Join)
work_mem = '256MB'

-- 維護記憶體(影響 HNSW 建立、VACUUM)
maintenance_work_mem = '2GB'

-- 平行查詢
max_parallel_workers_per_gather = 4

-- 平行建立 HNSW 索引
SET max_parallel_maintenance_workers = 7;

分批次插入

from psycopg2.extras import execute_values

def batch_insert(conn, records, batch_size=1000):
    """分批次插入向量,避免單次事務過大"""
    for i in range(0, len(records), batch_size):
        batch = records[i:i + batch_size]
        with conn.cursor() as cur:
            execute_values(
                cur,
                "INSERT INTO documents (content, embedding) VALUES %s",
                [(r["content"], r["embedding"]) for r in batch],
                template="(%s, %s::vector)"
            )
        conn.commit()
        print(f"已插入 {min(i + batch_size, len(records))}/{len(records)} 筆")

索引建立策略

-- 先大量插入,再建立索引(最快)
DROP INDEX IF EXISTS documents_embedding_idx;
-- 大量 INSERT ...

-- HNSW 百萬級參數建議
CREATE INDEX CONCURRENTLY documents_embedding_idx
    ON documents USING hnsw (embedding vector_cosine_ops)
    WITH (m = 16, ef_construction = 64);
-- m: 16(平衡效能與記憶體)
-- ef_construction: 64-128(品質,愈大愈慢但 Recall 更高)
-- ef_search: 40-100(視延遲容忍度調整)

量化加速(0.8.0+)

-- Scalar Quantization:float32 → int8,節省 75% 索引記憶體
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64, quantization = sq);

-- Binary Quantization:最激進壓縮,轉為 1-bit
-- 適合維度 >= 1024,搭配重新排序使用
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64, quantization = bq);

資料表分割

-- 對百萬級向量使用範圍分割
CREATE TABLE documents (
    id          BIGSERIAL,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    content     TEXT,
    embedding   vector(1536)
) PARTITION BY RANGE (created_at);

-- 按月建立分割
CREATE TABLE documents_2026_01 PARTITION OF documents
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

-- 在每個分割上建立 HNSW 索引
CREATE INDEX ON documents_2026_01 USING hnsw (embedding vector_cosine_ops);

效能監控

-- 監控索引大小
SELECT indexname,
       pg_size_pretty(pg_relation_size(indexname::regclass)) AS index_size
FROM pg_indexes
WHERE tablename = 'documents';

-- 查看快取命中率
SELECT
    heap_blks_hit::float / NULLIF(heap_blks_hit + heap_blks_read, 0) AS table_cache,
    idx_blks_hit::float / NULLIF(idx_blks_hit + idx_blks_read, 0) AS index_cache
FROM pg_statio_user_tables
WHERE relname = 'documents';

pgvector vs 專用向量資料庫

比較維度pgvectorPineconeQdrant
部署方式自行管理或雲端 PaaS全託管 SaaS自行部署或雲端
開源
關聯式資料整合原生(同一資料庫)有限
ACID 事務完整支援不支援部分支援
最大向量規模億級(調校後)十億級十億級
混合搜尋需自行實作原生支援原生支援
費用基礎設施成本依用量計費(較貴)基礎設施成本

適合 pgvector 的場景:已使用 PostgreSQL、需要 ACID 事務、向量資料與業務資料緊密結合、中小規模(< 1000 萬向量)、成本敏感。

不適合 pgvector 的場景:數十億向量且對延遲有毫秒級要求、純向量搜尋產品、需要原生複雜混合搜尋 API。

常見問題與陷阱

問題原因解決方案
查詢走 Seq Scan 不走索引資料量太少或 LIMIT 太大確保資料量足夠,LIMIT 在合理範圍(< 100)
Recall 太低HNSW 的 ef_search 或 IVFFlat 的 probes 太小增大 ef_search / probes
索引建立超慢maintenance_work_mem 太小設為 2GB+,增加 max_parallel_maintenance_workers
記憶體不足HNSW 記憶體消耗大改用 IVFFlat 或 halfvec 減少記憶體
向量維度不匹配插入向量維度與欄位定義不同確認 Embedding 模型的輸出維度
INSERT 效能差已有 HNSW 索引時逐筆插入分批插入,或先 DROP INDEX 再重建
距離函式選錯索引的 ops class 與查詢運算子不符確保 vector_cosine_ops 對應 <=> 運算子

版本演進

版本發布時間重要新功能
0.4.02023-05初始穩定版本,IVFFlat 索引
0.5.02023-09HNSW 索引、維度上限提升至 16,000
0.6.02024-01平行建立 HNSW 索引
0.7.02024-04halfvecsparsevecbit 型別
0.8.02024-11Scalar/Binary Quantization
-- 升級 pgvector Extension
ALTER EXTENSION vector UPDATE;

-- 升級後可能需要重建索引(尤其跨大版本)
REINDEX INDEX CONCURRENTLY documents_embedding_idx;

總結

pgvectorPostgreSQL 成為 AI 時代的一站式資料平台——你不需要額外維護一套向量資料庫,就能在熟悉的 SQL 環境中完成 Embedding 儲存、向量索引和語義搜尋。搭配 HNSW 索引和適當的效能調校,百萬級向量的查詢延遲可以控制在個位數毫秒。

關鍵要點:

  • HNSW 是大多數場景的首選索引,支援動態插入且 Recall 更高
  • 距離函式選擇取決於 Embedding 模型——OpenAI 等已正規化向量用餘弦距離或內積
  • 百萬級規模需要系統性調校:分批插入、先 DROP INDEX 再重建、調整 maintenance_work_mem
  • 混合搜尋(向量 + 全文)用 RRF 融合排名,能顯著提升搜尋品質

下一篇,我們將探討 PostGIS 地理空間——從空間資料型別、座標系統到幾何查詢與空間索引的完整實戰。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。