欢迎光临
我们一直在努力

PostgreSQL笔记48: 全文检索与向量检索的对比与混合检索实践

纲要

  • 信息检索的演进阶段
    • 关键词匹配
    • 全文检索
    • 语义检索(向量检索)
  • 全文检索(Full-Text Search)核心机制
    • tsvector 数据类型
    • tsquery 数据类型
    • to_tsvector 与 to_tsquery 函数
    • GIN 索引
    • ts_rank 排序函数
  • 向量检索(Vector Search)核心机制
    • Embedding 模型
    • 相似度计算
    • pgvector 插件
    • HNSW / IVFFlat 索引
  • 全文检索与向量检索的对比
  • 混合检索(Hybrid Search)实践
    • Pre-filter 与 Post-filter 策略
    • 统一 SQL 查询实现
  • API 速览
  • Demo 完整示例
  • 官方文档与参考链接

信息检索的演进:从关键词到语义

信息检索技术的发展大致经历了三个阶段。

第一阶段:关键词匹配(Keyword Matching) 。最初的搜索方式本质上是字符串匹配——用户输入什么,系统就在文档中查找完全相同的字符串。这种方式简单直接,但无法处理同义词、拼写变体等问题。

第二阶段:全文检索(Full-Text Search) 。随着倒排索引(Inverted Index)、分词(Tokenization)和词干提取(Stemming)等技术的成熟,系统能够在海量文本中高效检索关键词。Elasticsearch 是这一领域的代表性产品,PostgreSQL 则通过 tsvector / tsquery 类型与 GIN 索引提供了原生的全文检索能力[reference:0]。

第三阶段:语义检索(Semantic Search / Vector Search) 。进入 AI 与大模型时代,用户的输入越来越自然——可能是一句话、一段文字,甚至是一张图片或一段音频。系统需要理解语义而非仅仅匹配字符。向量检索通过 Embedding 模型将文本转换为向量,再通过相似度计算完成检索。

全文检索“快而准,但死板”——它只关心字符是否匹配。向量检索“懂语义,但模糊”——它能理解“轿车”与“汽车”的语义相似性,但召回率可能不稳定,索引维护成本也更高[reference:1]。

全文检索核心机制

PostgreSQL 自 8.3 版本开始支持全文检索功能(8.3 引入核心支持,后续版本持续增强)。其核心是倒排索引——将每个词(Lexeme)映射到包含它的文档列表,如同书籍的附录索引。

核心数据类型与函数

tsvector :全文检索的文档表示类型。它将文本分词、去除停用词(Stop Words)、词干归一化后,存储为词素(Lexeme)及其位置的排序列表[reference:2]。例如:

SELECT to_tsvector('english', 'a fat cat sat on a mat – it ate a fat rats');

返回结果中不包含 a、on、it 等停用词,rats 被归一化为 rat,标点符号被忽略[reference:3]。

tsquery :全文检索的查询表示类型。它存储待搜索的词素,并支持布尔运算符 &(AND)、|(OR)、!(NOT)以及短语搜索运算符 <->[reference:4][reference:5]。

to_tsvector :将文本转换为 tsvector 类型[reference:6]。

to_tsquery :将查询字符串转换为 tsquery 类型[reference:7]。

@@ 运算符:判断 tsvector 是否匹配 tsquery[reference:8]。

基础示例

— 创建测试表
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
title TEXT,
body TEXT,
tsv TSVECTOR
);

— 插入数据并生成 tsvector(使用 English 配置)
INSERT INTO documents (title, body, tsv)
VALUES (
'PostgreSQL Full-Text Search',
'PostgreSQL provides powerful full-text search capabilities.',
to_tsvector('english', 'PostgreSQL provides powerful full-text search capabilities.')
);

— 全文检索查询:查找包含 "postgresql" 和 "search" 的文档
SELECT title, body
FROM documents
WHERE tsv @@ to_tsquery('english', 'postgresql & search');

GIN 索引加速

对于生产环境,必须创建 GIN(Generalized Inverted Index)索引以加速全文检索[reference:9]:

CREATE INDEX idx_documents_tsv ON documents USING GIN (tsv);

GIN 索引是全文检索的首选索引类型,它存储 tsvector 中每个词素的索引条目[reference:10]。需要注意的是,表达式中必须显式指定文本搜索配置(如 'english'),否则索引无法被使用[reference:11]。

相关性排序

ts_rank 函数用于计算文档与查询的相关性分数,分数越高表示匹配程度越高[reference:12]:

SELECT
title,
ts_rank(tsv, to_tsquery('english', 'postgresql & search')) AS rank
FROM documents
WHERE tsv @@ to_tsquery('english', 'postgresql & search')
ORDER BY rank DESC;

向量检索核心机制

向量检索(Vector Search)通过 Embedding 模型将文本、图像、音频等数据转换为高维向量,然后通过计算向量间的相似度(如余弦相似度、欧几里得距离)来检索语义相近的内容。

PostgreSQL 通过 pgvector 扩展支持向量检索。pgvector 提供:

  • vector 数据类型
  • 余弦相似度运算符 <=>
  • 欧几里得距离运算符 <->
  • 内积运算符 <#>
  • HNSW 和 IVFFlat 索引

启用 pgvector

— 创建扩展
CREATE EXTENSION vector;

— 创建包含向量列的表
CREATE TABLE embeddings (
id SERIAL PRIMARY KEY,
content TEXT,
embedding VECTOR(768) — 维度取决于所使用的 Embedding 模型
);

— 创建 HNSW 索引(适用于高维向量)
CREATE INDEX idx_embeddings_hnsw ON embeddings
USING hnsw (embedding vector_cosine_ops);

— 或使用 IVFFlat 索引(适用于数据量较大的场景)
CREATE INDEX idx_embeddings_ivfflat ON embeddings
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);

向量相似度检索

— 余弦相似度检索(值越接近 1 越相似)
SELECT content, 1 – (embedding <=> '[0.1, 0.2, …]') AS similarity
FROM embeddings
ORDER BY embedding <=> '[0.1, 0.2, …]'
LIMIT 10;

全文检索与向量检索对比

维度全文检索向量检索
核心原理 倒排索引 + 分词 Embedding + 相似度计算
匹配方式 精确关键词匹配 语义相似度匹配
优势 速度快、精确、成熟稳定 理解语义、容错性强、跨模态
局限 不理解语义、灵活性差 召回率不稳定、索引维护成本高
适用场景 日志检索、明确关键词的结构化文本 自然语言提问、模糊语义搜索

全文检索与向量检索并非互斥关系,而是互补关系。全文检索在精确定位场景下表现出色,向量检索在自然语言处理与模糊语义场景中更加灵活。

混合检索(Hybrid Search)实践

在实际生产环境中,将全文检索与向量检索结合使用,可以实现“高效、精准、智能”的搜索体验。

数据表设计

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE hybrid_docs (
id SERIAL PRIMARY KEY,
title TEXT,
content TEXT,
tsv TSVECTOR,
embedding VECTOR(768)
);

— 创建 GIN 索引(全文检索)
CREATE INDEX idx_hybrid_docs_tsv ON hybrid_docs USING GIN (tsv);

— 创建 HNSW 索引(向量检索)
CREATE INDEX idx_hybrid_docs_embedding ON hybrid_docs
USING hnsw (embedding vector_cosine_ops);

策略一:先向量召回,再全文过滤(Post-filter)

先用向量检索召回语义相关的 Top-N 文档,再通过全文检索进行精确过滤:

WITH vector_results AS (
SELECT id, title, content, 1 – (embedding <=> '[0.1, 0.2, …]') AS similarity
FROM hybrid_docs
ORDER BY embedding <=> '[0.1, 0.2, …]'
LIMIT 100
)
SELECT *
FROM vector_results
WHERE tsv @@ to_tsquery('english', 'postgresql & search')
ORDER BY similarity DESC;

策略二:先全文过滤,再向量排序(Pre-filter)

先用全文检索过滤候选文档,再通过向量相似度进行排序:

WITH fulltext_results AS (
SELECT id, title, content, tsv
FROM hybrid_docs
WHERE tsv @@ to_tsquery('english', 'postgresql & search')
)
SELECT
id,
title,
content,
1 – (embedding <=> '[0.1, 0.2, …]') AS similarity
FROM fulltext_results
ORDER BY embedding <=> '[0.1, 0.2, …]'
LIMIT 20;

策略三:加权混合排序

结合全文检索的 ts_rank 分数与向量相似度分数,进行加权综合排序:

SELECT
id,
title,
content,
ts_rank(tsv, to_tsquery('english', 'postgresql & search')) AS text_score,
1 – (embedding <=> '[0.1, 0.2, …]') AS vector_score,
(0.5 * ts_rank(tsv, to_tsquery('english', 'postgresql & search')) +
0.5 * (1 – (embedding <=> '[0.1, 0.2, …]'))) AS hybrid_score
FROM hybrid_docs
WHERE tsv @@ to_tsquery('english', 'postgresql & search')
ORDER BY hybrid_score DESC
LIMIT 20;

API 速览

全文检索核心 API

函数 / 运算符签名说明
to_tsvector to_tsvector([config regconfig, ] document text) → tsvector 将文档文本转换为 tsvector[reference:13]
to_tsquery to_tsquery([config regconfig, ] query text) → tsquery 将查询字符串转换为 tsquery[reference:14]
plainto_tsquery plainto_tsquery([config regconfig, ] query text) → tsquery 将纯文本转换为 tsquery,自动添加 & 运算符[reference:15]
phraseto_tsquery phraseto_tsquery([config regconfig, ] query text) → tsquery 将纯文本转换为短语查询 tsquery[reference:16]
ts_rank ts_rank([weights float4[], ] vector tsvector, query tsquery [, normalization integer ]) → float4 计算文档与查询的相关性分数
setweight setweight(vector tsvector, weight "char") → tsvector 为 tsvector 中的词素标记权重(A/B/C/D)[reference:17]
@@ tsvector @@ tsquery → boolean 判断 tsvector 是否匹配 tsquery[reference:18]
` `

pgvector 核心 API

函数 / 运算符签名说明
vector vector(n) 创建 n 维向量类型
<=> vector <=> vector → float 余弦距离(值越小越相似)
<-> vector <-> vector → float 欧几里得距离
<#> vector <#> vector → float 负内积

Demo 完整示例

以下是一个完整的 Node.js + PostgreSQL 混合检索示例,使用 pg 驱动连接数据库。

运行说明

  • 确保 PostgreSQL 已安装 pgvector 扩展
  • 执行下方 SQL 初始化脚本
  • 安装依赖:npm install pg
  • 运行脚本:node hybrid-search-demo.js
  • 初始化 SQL

    — 启用 pgvector 扩展
    CREATE EXTENSION IF NOT EXISTS vector;

    — 创建混合检索表
    DROP TABLE IF EXISTS hybrid_docs;
    CREATE TABLE hybrid_docs (
    id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    tsv TSVECTOR,
    embedding VECTOR(3) — 使用 3 维向量便于演示
    );

    — 插入示例数据
    INSERT INTO hybrid_docs (title, content, tsv, embedding) VALUES
    (
    'PostgreSQL Full-Text Search Guide',
    'PostgreSQL provides built-in full-text search using tsvector and tsquery.',
    to_tsvector('english', 'PostgreSQL provides built-in full-text search using tsvector and tsquery.'),
    '[0.9, 0.1, 0.2]'::VECTOR(3)
    ),
    (
    'Vector Search with pgvector',
    'pgvector is a PostgreSQL extension that enables vector similarity search.',
    to_tsvector('english', 'pgvector is a PostgreSQL extension that enables vector similarity search.'),
    '[0.1, 0.8, 0.3]'::VECTOR(3)
    ),
    (
    'Hybrid Search Combining Full-Text and Vectors',
    'Combining full-text search and vector search delivers better search results.',
    to_tsvector('english', 'Combining full-text search and vector search delivers better search results.'),
    '[0.6, 0.6, 0.4]'::VECTOR(3)
    ),
    (
    'Database Indexing Strategies',
    'GIN indexes accelerate full-text search, while HNSW indexes accelerate vector search.',
    to_tsvector('english', 'GIN indexes accelerate full-text search, while HNSW indexes accelerate vector search.'),
    '[0.2, 0.3, 0.9]'::VECTOR(3)
    ),
    (
    'Introduction to Machine Learning',
    'Machine learning models can generate embeddings for text, images, and audio.',
    to_tsvector('english', 'Machine learning models can generate embeddings for text, images, and audio.'),
    '[0.3, 0.2, 0.7]'::VECTOR(3)
    );

    — 创建 GIN 索引(全文检索)
    CREATE INDEX idx_hybrid_docs_tsv ON hybrid_docs USING GIN (tsv);

    — 创建 HNSW 索引(向量检索)
    CREATE INDEX idx_hybrid_docs_embedding ON hybrid_docs
    USING hnsw (embedding vector_cosine_ops);

    Node.js 代码

    const { Pool } = require('pg');

    const pool = new Pool({
    host: 'localhost',
    port: 5432,
    database: 'postgres',
    user: 'postgres',
    password: 'your_password',
    });

    // 查询向量(模拟用户输入 "PostgreSQL search" 的 Embedding)
    const QUERY_VECTOR = [0.8, 0.2, 0.3];

    async function hybridSearch(queryText, queryVector, limit = 10) {
    const client = await pool.connect();
    try {
    // 混合检索:先全文过滤,再按向量相似度排序
    const result = await client.query(
    `
    WITH filtered AS (
    SELECT
    id,
    title,
    content,
    tsv,
    embedding,
    ts_rank(tsv, to_tsquery('english', $1)) AS text_score
    FROM hybrid_docs
    WHERE tsv @@ to_tsquery('english', $1)
    )
    SELECT
    id,
    title,
    content,
    text_score,
    1 – (embedding <=> $2::VECTOR(3)) AS vector_score,
    (0.5 * ts_rank(tsv, to_tsquery('english', $1)) +
    0.5 * (1 – (embedding <=> $2::VECTOR(3)))) AS hybrid_score
    FROM filtered
    ORDER BY hybrid_score DESC
    LIMIT $3
    `
    ,
    [queryText, `[${QUERY_VECTOR.join(',')}]`, limit]
    );
    return result.rows;
    } finally {
    client.release();
    }
    }

    async function main() {
    console.log('=== PostgreSQL 混合检索 Demo ===\\n');

    const results = await hybridSearch('postgresql & search', QUERY_VECTOR);

    console.log('查询条件: "postgresql & search"');
    console.log('查询向量:', QUERY_VECTOR);
    console.log('\\n检索结果:');
    console.log('-' .repeat(80));

    results.forEach((row, idx) => {
    console.log(`\\n[${idx + 1}] ${row.title}`);
    console.log(` Text Score: ${row.text_score.toFixed(4)}`);
    console.log(` Vector Score: ${row.vector_score.toFixed(4)}`);
    console.log(` Hybrid Score: ${row.hybrid_score.toFixed(4)}`);
    console.log(` Content: ${row.content}`);
    });

    await pool.end();
    }

    main().catch(console.error);

    技术点总结

    • 全文检索:使用 tsvector、tsquery、to_tsvector、to_tsquery 和 @@ 运算符
    • 向量检索:使用 pgvector 的 vector 类型和余弦距离运算符 <=>
    • 混合检索:在同一 SQL 查询中结合全文过滤与向量排序
    • 加权排序:使用 ts_rank 和向量相似度计算综合分数
    • 索引加速:GIN 索引加速全文检索,HNSW 索引加速向量检索

    多语言示例

    本部分提供 Go、Python 和 Java 三种语言的完整可运行示例,功能与前述 Node.js 版本等价:连接 PostgreSQL 数据库,执行混合检索(全文过滤 + 向量相似度排序),输出综合排序结果。

    Go 示例

    运行说明
  • 安装依赖:

    go mod init hybrid-search-demo
    go get github.com/jackc/pgx/v5
    go get github.com/pgvector/pgvector-go
    go get github.com/pgvector/pgvector-go/pgx

  • 数据库准备:确保 PostgreSQL 已安装 pgvector 扩展,并执行 Node.js 示例中的初始化 SQL 脚本。

  • 运行:

    go run main.go

  • 代码说明

    Go 示例使用 pgx 作为 PostgreSQL 驱动,pgvector-go 提供 vector 类型的支持[reference:0]。核心流程如下:

    • 通过 pgx.Connect 建立数据库连接
    • 使用 pgxvec.RegisterTypes 注册 vector 类型
    • 执行混合检索 SQL,使用 pgvector.NewVector 构造查询向量
    • 使用余弦距离运算符 <=> 计算相似度
    技术点总结
    • 使用 pgx 驱动 + pgvector-go 扩展
    • 通过 pgxvec.RegisterTypes 注册自定义类型
    • 使用 pgvector.NewVector 将 Go 切片转换为数据库 vector 类型
    • 支持 <=> 余弦距离、<-> 欧几里得距离、<#> 负内积运算符
    完整代码

    package main

    import (
    "context"
    "fmt"
    "log"

    "github.com/jackc/pgx/v5"
    "github.com/pgvector/pgvector-go"
    pgxvec "github.com/pgvector/pgvector-go/pgx"
    )

    type HybridDoc struct {
    ID int
    Title string
    Content string
    TextScore float64
    VectorScore float64
    HybridScore float64
    }

    func main() {
    ctx := context.Background()

    // 连接数据库
    conn, err := pgx.Connect(ctx, "postgres://postgres:your_password@localhost:5432/postgres")
    if err != nil {
    log.Fatal("无法连接数据库:", err)
    }
    defer conn.Close(ctx)

    // 注册 pgvector 类型
    if err := pgxvec.RegisterTypes(ctx, conn); err != nil {
    log.Fatal("注册 vector 类型失败:", err)
    }

    // 启用扩展(如未启用)
    _, err = conn.Exec(ctx, "CREATE EXTENSION IF NOT EXISTS vector")
    if err != nil {
    log.Fatal("启用 vector 扩展失败:", err)
    }

    // 查询向量(模拟用户输入 "PostgreSQL search" 的 Embedding)
    queryVector := pgvector.NewVector([]float32{0.8, 0.2, 0.3})
    queryText := "postgresql & search"
    limit := 10

    // 混合检索 SQL
    sql := `
    WITH filtered AS (
    SELECT
    id,
    title,
    content,
    tsv,
    embedding,
    ts_rank(tsv, to_tsquery('english', $1)) AS text_score
    FROM hybrid_docs
    WHERE tsv @@ to_tsquery('english', $1)
    )
    SELECT
    id,
    title,
    content,
    text_score,
    1 – (embedding <=> $2) AS vector_score,
    (0.5 * ts_rank(tsv, to_tsquery('english', $1)) +
    0.5 * (1 – (embedding <=> $2))) AS hybrid_score
    FROM filtered
    ORDER BY hybrid_score DESC
    LIMIT $3
    `

    rows, err := conn.Query(ctx, sql, queryText, queryVector, limit)
    if err != nil {
    log.Fatal("查询失败:", err)
    }
    defer rows.Close()

    fmt.Println("=== Go 混合检索 Demo ===")
    fmt.Printf("查询条件: %q\\n", queryText)
    fmt.Printf("查询向量: [0.8, 0.2, 0.3]\\n\\n")
    fmt.Println("检索结果:")
    fmt.Println("——————————————————————————–")

    var results []HybridDoc
    for rows.Next() {
    var doc HybridDoc
    err := rows.Scan(
    &doc.ID,
    &doc.Title,
    &doc.Content,
    &doc.TextScore,
    &doc.VectorScore,
    &doc.HybridScore,
    )
    if err != nil {
    log.Fatal("扫描行失败:", err)
    }
    results = append(results, doc)
    }

    for i, doc := range results {
    fmt.Printf("\\n[%d] %s\\n", i+1, doc.Title)
    fmt.Printf(" Text Score: %.4f\\n", doc.TextScore)
    fmt.Printf(" Vector Score: %.4f\\n", doc.VectorScore)
    fmt.Printf(" Hybrid Score: %.4f\\n", doc.HybridScore)
    fmt.Printf(" Content: %s\\n", doc.Content)
    }
    }

    Python 示例

    运行说明
  • 安装依赖:

    pip install psycopg2-binary pgvector

  • 数据库准备:确保 PostgreSQL 已安装 pgvector 扩展,并执行 Node.js 示例中的初始化 SQL 脚本。

  • 运行:

    python hybrid_search_demo.py

  • 代码说明

    Python 示例使用 psycopg2 作为 PostgreSQL 驱动,pgvector.psycopg2 提供 vector 类型的支持[reference:5]。核心流程如下:

    • 使用 psycopg2.connect 建立数据库连接
    • 调用 register_vector 注册 vector 类型[reference:6][reference:7]
    • 执行混合检索 SQL,使用列表作为向量参数
    • 使用余弦距离运算符 <=> 计算相似度
    技术点总结
    • 使用 psycopg2-binary + pgvector Python 包
    • 通过 register_vector 注册自定义类型
    • 向量参数以 Python list 形式传递,自动转换为 PostgreSQL vector
    • 支持 <=> 余弦距离、<-> 欧几里得距离、<#> 负内积运算符
    完整代码

    import psycopg2
    from pgvector.psycopg2 import register_vector

    def main():
    # 连接数据库
    conn = psycopg2.connect(
    host="localhost",
    port=5432,
    database="postgres",
    user="postgres",
    password="your_password"
    )
    cur = conn.cursor()

    # 注册 pgvector 类型
    register_vector(conn)

    # 启用扩展(如未启用)
    cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
    conn.commit()

    # 查询向量(模拟用户输入 "PostgreSQL search" 的 Embedding)
    query_vector = [0.8, 0.2, 0.3]
    query_text = "postgresql & search"
    limit = 10

    # 混合检索 SQL
    sql = """
    WITH filtered AS (
    SELECT
    id,
    title,
    content,
    tsv,
    embedding,
    ts_rank(tsv, to_tsquery('english', %s)) AS text_score
    FROM hybrid_docs
    WHERE tsv @@ to_tsquery('english', %s)
    )
    SELECT
    id,
    title,
    content,
    text_score,
    1 – (embedding <=> %s) AS vector_score,
    (0.5 * ts_rank(tsv, to_tsquery('english', %s)) +
    0.5 * (1 – (embedding <=> %s))) AS hybrid_score
    FROM filtered
    ORDER BY hybrid_score DESC
    LIMIT %s
    """

    cur.execute(sql, (query_text, query_text, query_vector, query_text, query_vector, limit))
    results = cur.fetchall()

    print("=== Python 混合检索 Demo ===")
    print(f'查询条件: "{query_text}"')
    print(f"查询向量: {query_vector}\\n")
    print("检索结果:")
    print("-" * 80)

    for i, row in enumerate(results):
    doc_id, title, content, text_score, vector_score, hybrid_score = row
    print(f"\\n[{i+1}] {title}")
    print(f" Text Score: {text_score:.4f}")
    print(f" Vector Score: {vector_score:.4f}")
    print(f" Hybrid Score: {hybrid_score:.4f}")
    print(f" Content: {content}")

    cur.close()
    conn.close()

    if __name__ == "__main__":
    main()

    Java 示例

    运行说明
  • Maven 依赖(pom.xml):

    <dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.3</version>
    </dependency>
    <dependency>
    <groupId>com.pgvector</groupId>
    <artifactId>pgvector</artifactId>
    <version>0.1.6</version>
    </dependency>

  • 数据库准备:确保 PostgreSQL 已安装 pgvector 扩展,并执行 Node.js 示例中的初始化 SQL 脚本。

  • 编译与运行:

    mvn compile
    mvn exec:java -Dexec.mainClass="com.example.HybridSearchDemo"

  • 代码说明

    Java 示例使用 JDBC 作为 PostgreSQL 驱动,pgvector 库提供 PGvector 类型支持[reference:9]。核心流程如下:

    • 使用 DriverManager.getConnection 建立数据库连接
    • 调用 PGvector.registerTypes 注册 vector 类型
    • 执行混合检索 SQL,使用 PGvector 对象构造查询向量
    • 使用余弦距离运算符 <=> 计算相似度[reference:12]
    技术点总结
    • 使用 JDBC + pgvector Java 库
    • 通过 PGvector.registerTypes 注册自定义类型
    • 使用 setObject 传递 PGvector 参数
    • 支持 <=> 余弦距离、<-> 欧几里得距离、<#> 负内积运算符
    完整代码

    package com.example;

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;

    import com.pgvector.PGvector;

    public class HybridSearchDemo {

    public static void main(String[] args) {
    String url = "jdbc:postgresql://localhost:5432/postgres";
    String user = "postgres";
    String password = "your_password";

    try (Connection conn = DriverManager.getConnection(url, user, password)) {

    // 注册 pgvector 类型
    PGvector.registerTypes(conn);

    // 启用扩展(如未启用)
    try (PreparedStatement stmt = conn.prepareStatement(
    "CREATE EXTENSION IF NOT EXISTS vector")) {
    stmt.executeUpdate();
    }

    // 查询向量(模拟用户输入 "PostgreSQL search" 的 Embedding)
    float[] vectorData = {0.8f, 0.2f, 0.3f};
    PGvector queryVector = new PGvector(vectorData);
    String queryText = "postgresql & search";
    int limit = 10;

    // 混合检索 SQL
    String sql = """
    WITH filtered AS (
    SELECT
    id,
    title,
    content,
    tsv,
    embedding,
    ts_rank(tsv, to_tsquery('english', ?)) AS text_score
    FROM hybrid_docs
    WHERE tsv @@ to_tsquery('english', ?)
    )
    SELECT
    id,
    title,
    content,
    text_score,
    1 – (embedding <=> ?) AS vector_score,
    (0.5 * ts_rank(tsv, to_tsquery('english', ?)) +
    0.5 * (1 – (embedding <=> ?))) AS hybrid_score
    FROM filtered
    ORDER BY hybrid_score DESC
    LIMIT ?
    """
    ;

    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
    pstmt.setString(1, queryText);
    pstmt.setString(2, queryText);
    pstmt.setObject(3, queryVector);
    pstmt.setString(4, queryText);
    pstmt.setObject(5, queryVector);
    pstmt.setInt(6, limit);

    ResultSet rs = pstmt.executeQuery();

    System.out.println("=== Java 混合检索 Demo ===");
    System.out.printf("查询条件: \\"%s\\"\\n", queryText);
    System.out.printf("查询向量: [0.8, 0.2, 0.3]\\n\\n");
    System.out.println("检索结果:");
    System.out.println("——————————————————————————–");

    int index = 0;
    while (rs.next()) {
    index++;
    int id = rs.getInt("id");
    String title = rs.getString("title");
    String content = rs.getString("content");
    double textScore = rs.getDouble("text_score");
    double vectorScore = rs.getDouble("vector_score");
    double hybridScore = rs.getDouble("hybrid_score");

    System.out.printf("\\n[%d] %s\\n", index, title);
    System.out.printf(" Text Score: %.4f\\n", textScore);
    System.out.printf(" Vector Score: %.4f\\n", vectorScore);
    System.out.printf(" Hybrid Score: %.4f\\n", hybridScore);
    System.out.printf(" Content: %s\\n", content);
    }
    }

    } catch (SQLException e) {
    System.err.println("数据库错误: " + e.getMessage());
    e.printStackTrace();
    }
    }
    }

    多语言对比

    维度Node.jsGoPythonJava
    驱动/库 pg pgx + pgvector-go psycopg2 + pgvector JDBC + pgvector
    类型注册 自动(pg 驱动原生支持) pgxvec.RegisterTypes register_vector PGvector.registerTypes
    向量构造 [0.8, 0.2, 0.3](数组字面量) pgvector.NewVector([]float32{…}) [0.8, 0.2, 0.3](列表) new PGvector(new float[]{…})
    参数传递 $1, $2 占位符 $1, $2 占位符 %s 占位符(psycopg2) ? 占位符(JDBC)
    异步支持 原生 async/await 原生 context + goroutine 需 asyncpg 或 asyncio 需 CompletableFuture / R2DBC
    ORM 可选 TypeORM / Prisma GORM / Bun / Ent SQLAlchemy[reference:14] Hibernate / Spring JDBC
    依赖管理 npm go mod pip Maven / Gradle
    类型安全 弱(TypeScript 可选) 强 弱 强

    官方文档

    • PostgreSQL Full Text Search (Chapter 12)
    • PostgreSQL Text Search Functions (Section 9.13)
    • PostgreSQL Text Search Types (Section 8.11)
    • PostgreSQL GIN Indexes
    • pgvector GitHub Repository
    • pgvector Documentation

    参考链接

    • PostgreSQL Full Text Search Tutorial
    • pgvector – Open Source Vector Similarity Search for PostgreSQL
    • Elasticsearch vs PostgreSQL Full-Text Search

    总结

    本文系统梳理了 PostgreSQL 全文检索与向量检索的核心机制、适用场景及实现方式。全文检索以倒排索引为核心,通过 tsvector / tsquery 类型和 GIN 索引实现高效的关键词匹配;向量检索通过 pgvector 扩展和 Embedding 模型实现语义级别的相似度检索。二者并非互斥,而是互补关系——全文检索擅长精确定位,向量检索擅长语义理解。

    通过混合检索策略(Pre-filter / Post-filter / 加权排序),可以在同一 PostgreSQL 实例中实现兼具效率与智能的搜索体验。PostgreSQL 同时原生支持全文检索与向量检索,是构建现代检索系统的理想数据库选型。

    赞(0)
    未经允许不得转载:171主机测评 » PostgreSQL笔记48: 全文检索与向量检索的对比与混合检索实践
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址