PostgreSQL笔记41:向量数据、向量化表示与pgvector扩展实践

PostgreSQL pgvector:如何利用向量数据库提升搜索效率和精度 随着基础模型的兴起,向量数据库的受欢迎程度也飙升。事实上,在大型语言模型环境中,向量数据库也很有用。在机器学习领域,我们经常处理的是向量嵌入。向量嵌入是通过特定的机器学习模型运行对象的特征,将对象的上下文信息投射到潜在空间中来创建的。为了在使用向量嵌入时能够表现得特别好,创建向量数据库是必要的。这方面的工作包括存储、更新和检索向量。当我们谈论检索时,通常是指检索与查询最相似的向量,这些向量与嵌入到同一潜在空间并传递到向量数据库中。这个检索过程被称为近似最近邻。 阅读详情

概述

在人工智能与机器学习应用迅速普及的背景下,非结构化数据(文本、图像、音频等)的存储与检索需求日益增长。向量数据库作为AI时代的核心基础设施,其底层原理与技术实现备受关注。

PostgreSQL通过开源扩展 pgvector,将向量相似性搜索能力无缝集成到关系型数据库中。本文将从向量数据的基本概念出发,结合 pgvector 扩展,系统性地阐述向量化表示的技术原理与实践方法。

纲要

  • 向量数据的基本概念
    • 向量的数学定义与几何意义
    • 向量化表示(Vector Embedding)的核心思想
    • 多维度特征空间与事物区分
  • pgvector 扩展概述
    • 扩展安装与启用
    • 向量数据类型:vector、halfvec、sparsevec、bit
    • 距离算子与相似性度量
  • 向量数据的存储与操作
    • 创建向量数据表
    • 插入向量数据
    • 向量检索(精确搜索与近似搜索)
  • 向量索引与性能优化
    • IVFFlat 索引
    • HNSW 索引
    • 索引调优与并发创建
  • 向量嵌入(Embedding)生成与集成
    • 非结构化数据到向量的转换流程
    • 与外部嵌入模型(如OpenAI Embeddings API)的集成
  • API速览
  • Demo完整示例(Node.js + pg + pgvector)
  • 项目难点与解决方案(可选)
  • 官方文档与参考链接
  • 总结

向量数据的基本概念

向量的数学定义

根据数学定义,向量(Vector)是同时具有数值(大小)和方向的几何对象,且满足平行四边形法则。在理论数学中,向量被定义为向量空间中的元素,通常以带箭头的符号表示。向量的长度(模)代表其大小,箭头的指向代表其方向。

向量化表示的核心思想

向量化表示(Vector Embedding)是将非结构化数据(文本、图像、音频等)转换为高维向量数值的过程。通过这种嵌入(Embedding)技术,任何数据对象都可以被表示为一个多维向量,从而将现实世界的语义关系转化为数学空间中的几何关系。

非结构化数据

嵌入模型
Embedding Model

高维向量
High-Dimensional Vector

向量数据库
Vector Database

文本

图像

音频

多维度特征空间与事物区分

人类识别不同事物的本质是通过提取事物的多个特征。以区分犬种为例:

  • 单一维度(体型) :仅凭体型难以区分体型相近的犬种(如哈士奇、金毛、拉布拉多)
  • 二维特征空间(体型 + 毛发) :增加毛发特征后,可以在二维坐标系中区分上述三种犬
  • 高维特征空间 :继续增加眼睛大小、鼻子长短、攻击性、服从性等特征,维度越高,区分能力越强

理论上,只要特征维度足够多,任何两个不同的事物都可以在高维空间中被区分开。这正是向量化表示的核心价值——将现实世界的复杂对象映射到高维向量空间,使得语义相近的对象在空间中彼此靠近,语义相远的对象在空间中彼此远离。

高维空间:清晰区分

低维空间:难以区分

哈士奇

金毛

拉布拉多

哈士奇

特征向量1
体型

特征向量2
毛发

特征向量N
其他特征

pgvector扩展概述

pgvector 是PostgreSQL的开源扩展,为数据库添加了向量数据类型和向量相似性搜索能力。该扩展支持精确最近邻搜索和近似最近邻搜索(ANN),并提供了多种距离度量方式。

扩展安装与启用

pgvector 扩展需要通过 CREATE EXTENSION 命令启用:

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

若需要安装特定版本:

CREATE EXTENSION vector VERSION '0.7.4';

向量数据类型

pgvector 提供了多种向量数据类型以适应不同场景:

数据类型精度最大维度适用场景
vector单精度浮点数(float32)2,000通用场景,最常用
halfvec半精度浮点数(float16)4,000内存敏感场景
sparsevec稀疏向量16,000高维稀疏数据
bit二进制向量64,000二进制嵌入

距离算子

pgvector 提供以下距离算子用于向量相似性度量:

算子距离类型说明适用场景
<->L2距离(欧几里得距离)计算两点间的直线距离通用场景,幅度重要时
<=>余弦距离比较向量间的夹角文本嵌入,方向比幅度重要
<#>负内积返回负内积以便升序排列归一化向量,推荐系统
<+>L1距离(曼哈顿距离)计算各维度绝对差之和罕见场景

向量数据的存储与操作

创建向量数据表

创建包含向量列的数据表时,需指定向量的维度:

-- 创建一个3维向量存储表
CREATE TABLE items (
    id BIGSERIAL PRIMARY KEY,
    embedding VECTOR(3) NOT NULL,
    description TEXT
);

-- 创建一个1536维向量表(适配 OpenAI text-embedding-3-small 模型)
CREATE TABLE documents (
    id BIGSERIAL PRIMARY KEY,
    content TEXT,
    embedding VECTOR(1536)
);

插入向量数据

向量数据以数组形式插入:

-- 插入3维向量
INSERT INTO items (embedding, description) VALUES
    ('[0.1, 0.2, 0.3]', '样本A'),
    ('[0.5, 0.1, 0.8]', '样本B'),
    ('[0.3, 0.4, 0.2]', '样本C');

-- 批量插入使用 COPY 可获得更好的性能
COPY items (embedding, description) FROM STDIN;

向量检索

精确最近邻搜索

精确搜索逐一比较所有向量与查询向量的距离:

-- 使用 L2 距离查找与目标向量最相似的项
SELECT id, description, embedding <-> '[0.2, 0.1, 0.3]' AS distance
FROM items
ORDER BY embedding <-> '[0.2, 0.1, 0.3]'
LIMIT 5;

-- 使用余弦距离(适用于文本嵌入)
SELECT id, content, 1 - (embedding <=> query_embedding) AS similarity
FROM documents
ORDER BY embedding <=> query_embedding
LIMIT 5;

向量索引与性能优化

对于大规模向量数据集,精确搜索的线性扫描成本过高。pgvector 提供了两种近似最近邻(ANN)索引来加速查询。

IVFFlat索引

IVFFlat(Inverted File with Flat Compression)通过聚类将向量空间划分为多个倒排列表,查询时仅搜索与目标向量最近的几个列表。

-- 创建 IVFFlat 索引(需在表中已有数据后创建)
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops)
WITH (lists = 100);

-- 余弦距离的 IVFFlat 索引
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 1000);

IVFFlat 关键参数:

  • lists:倒排列表数量,通常设置为 rows / 1000,取值范围 100~1000
  • probes:查询时探测的列表数量(通过 SET ivfflat.probes = 10 调整),值越大召回率越高但查询越慢

HNSW索引

HNSW(Hierarchical Navigable Small World)构建多层图结构,查询时在图结构中导航而非扫描全表,通常作为默认首选。

-- 创建 HNSW 索引
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)
WITH (m = 16, ef_construction = 64);

-- 余弦距离的 HNSW 索引
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

HNSW 关键参数:

  • m:每个节点的最大连接数,默认16,值越大召回率越高但索引更大
  • ef_construction:构建时动态候选列表大小,默认64,值越大索引质量越高但构建更慢
  • ef_search:查询时动态候选列表大小(通过 SET hnsw.ef_search = 100 调整)

索引类型对比

特性IVFFlatHNSW
构建速度快慢
查询速度较快(probes调优后)快
召回率中高(取决于lists和probes)高
内存占用低高(存储图结构)
适用场景超大规模数据集(亿级)中小规模(千万级),追求召回率

索引创建注意事项:

  • IVFFlat 索引必须在表中已存在数据后创建,否则效果不佳
  • HNSW 索引可随时创建,但构建时间较长
  • 两种索引都支持并行创建(PostgreSQL 11+),可通过 CREATE INDEX CONCURRENTLY 避免阻塞写入
-- 并发创建索引(不阻塞读写)
CREATE INDEX CONCURRENTLY idx_items_embedding ON items USING ivfflat (embedding vector_l2_ops);

向量嵌入(Embedding)生成与集成

实际应用中,需先将非结构化数据转换为向量。常见做法是调用外部嵌入模型API或使用本地模型。

转换流程

数据库层

嵌入模型

应用层

原始文本/图像/音频

调用嵌入模型

返回向量数组

存储到 vector 列

执行相似性查询

与OpenAI Embeddings API集成示例(Node.js)

import OpenAI from 'openai';
import { Client } from 'pg';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const pgClient = new Client({ connectionString: process.env.DATABASE_URL });

async function generateEmbedding(text: string): Promise<number[]> {
    const response = await openai.embeddings.create({
        model: 'text-embedding-3-small',
        input: text,
    });
    return response.data[0].embedding;
}

async function storeDocument(content: string) {
    const embedding = await generateEmbedding(content);
    await pgClient.query(
        'INSERT INTO documents (content, embedding) VALUES ($1, $2)',
        [content, `[${embedding.join(',')}]`]
    );
}

async function searchSimilar(query: string, limit: number = 5) {
    const queryEmbedding = await generateEmbedding(query);
    const result = await pgClient.query(
        `SELECT content, 1 - (embedding <=> $1::vector) AS similarity
         FROM documents
         ORDER BY embedding <=> $1::vector
         LIMIT $2`,
        [`[${queryEmbedding.join(',')}]`, limit]
    );
    return result.rows;
}

API速览

本博客涉及的核心API主要包括 pgvector 扩展的SQL操作符和函数,以及与向量嵌入相关的Node.js库。

1. 向量类型构造函数

vector 类型支持从字符串或数组字面量构造:

-- 字符串构造
SELECT '[0.1, 0.2, 0.3]'::vector;

-- 数组构造(PostgreSQL 数组)
SELECT ARRAY[0.1, 0.2, 0.3]::vector;

2. 距离操作符

所有操作符返回浮点数,用于 ORDER BY 子句:

操作符签名返回类型示例
<->vector <-> vectorfloat8SELECT embedding <-> '[1,2,3]'
<=>vector <=> vectorfloat8SELECT embedding <=> '[1,2,3]'
<#>vector <#> vectorfloat8SELECT embedding <#> '[1,2,3]'
<+>vector <+> vectorfloat8SELECT embedding <+> '[1,2,3]'

3. 辅助函数

  • vector_dims(vector):返回向量的维度
  • vector_norm(vector):返回向量的L2范数
  • l2_distance(vector, vector):等同于 <->
  • cosine_distance(vector, vector):等同于 <=>
  • inner_product(vector, vector):返回内积

4. 索引访问方法操作符类

索引方法操作符类距离类型
ivfflat / hnswvector_l2_opsL2距离
ivfflat / hnswvector_cosine_ops余弦距离
ivfflat / hnswvector_ip_ops内积(负内积)

Demo完整示例

本节提供一个完整的Node.js应用,演示如何使用 pg 驱动连接PostgreSQL,创建带有 vector 列的表,插入数据,并进行相似性搜索。

运行说明

  1. 环境准备:

    • PostgreSQL 14+ 安装并启动
    • 安装 pgvector 扩展(参考官方文档)
    • Node.js 18+
  2. 项目初始化:

    mkdir pgvector-demo && cd pgvector-demo
    npm init -y
    npm install pg dotenv
    
  3. 数据库配置(.env 文件):

    DATABASE_URL=postgresql://user:password@localhost:5432/mydb
    
  4. 启动脚本:运行 node demo.js

代码说明

demo.js 文件完成以下操作:

  • 连接数据库并启用 vector 扩展
  • 删除并创建 embeddings 表
  • 插入三条样本向量(3维)
  • 执行精确L2距离搜索和余弦距离搜索
  • 展示结果
// demo.js
import { Client } from 'pg';
import dotenv from 'dotenv';
dotenv.config();

const client = new Client({
    connectionString: process.env.DATABASE_URL,
});

async function run() {
    await client.connect();
    console.log('Connected to PostgreSQL');

    // 1. 启用扩展
    await client.query('CREATE EXTENSION IF NOT EXISTS vector');
    console.log('pgvector extension enabled');

    // 2. 创建表(如果存在则删除重建)
    await client.query('DROP TABLE IF EXISTS items');
    await client.query(`
        CREATE TABLE items (
            id SERIAL PRIMARY KEY,
            embedding VECTOR(3) NOT NULL,
            label TEXT
        )
    `);
    console.log('Table "items" created');

    // 3. 插入示例数据
    const samples = [
        { label: 'Dog', vec: [0.9, 0.1, 0.2] },
        { label: 'Cat', vec: [0.2, 0.8, 0.1] },
        { label: 'Bird', vec: [0.1, 0.2, 0.9] },
        { label: 'Fish', vec: [0.3, 0.3, 0.3] },
    ];
    for (const s of samples) {
        await client.query(
            'INSERT INTO items (embedding, label) VALUES ($1, $2)',
            [`[${s.vec.join(',')}]`, s.label]
        );
    }
    console.log(`Inserted ${samples.length} items`);

    // 4. 精确 L2 距离搜索(查询向量 [0.8, 0.2, 0.1])
    const queryVec = [0.8, 0.2, 0.1];
    console.log('\n--- L2 Distance Search ---');
    const resL2 = await client.query(
        `SELECT label, embedding <-> $1::vector AS distance
         FROM items
         ORDER BY embedding <-> $1::vector
         LIMIT 3`,
        [`[${queryVec.join(',')}]`]
    );
    resL2.rows.forEach(row => {
        console.log(`Label: ${row.label}, Distance: ${row.distance.toFixed(4)}`);
    });

    // 5. 余弦距离搜索
    console.log('\n--- Cosine Distance Search ---');
    const resCos = await client.query(
        `SELECT label, embedding <=> $1::vector AS cosine_dist
         FROM items
         ORDER BY embedding <=> $1::vector
         LIMIT 3`,
        [`[${queryVec.join(',')}]`]
    );
    resCos.rows.forEach(row => {
        console.log(`Label: ${row.label}, Cosine Dist: ${row.cosine_dist.toFixed(4)}`);
    });

    await client.end();
}

run().catch(console.error);

输出示例:

Connected to PostgreSQL
pgvector extension enabled
Table "items" created
Inserted 4 items

--- L2 Distance Search ---
Label: Dog, Distance: 0.1414
Label: Fish, Distance: 0.5385
Label: Bird, Distance: 1.2166

--- Cosine Distance Search ---
Label: Dog, Cosine Dist: 0.0059
Label: Fish, Cosine Dist: 0.1667
Label: Bird, Cosine Dist: 0.4079

技术点总结

  • 使用 pg 原生驱动执行SQL,直接操作 vector 类型
  • 展示了 CREATE EXTENSION、建表、插入和查询的完整流程
  • 对比了L2距离和余弦距离在相同查询下的不同结果,体现度量选择的影响
  • 所有SQL均可直接在psql中运行,便于调试

多语言示例

基于前文 Node.js 版本的完整示例,本节提供 Go、Python 和 Java 三种语言的等价实现,涵盖数据库连接、pgvector 扩展启用、向量表创建、数据插入以及 L2 距离与余弦距离的相似性搜索。所有示例均使用各自语言的官方或主流 PostgreSQL 驱动,并保持相同的测试数据与查询逻辑,便于横向对比。

Go 语言示例

使用 pgx 驱动(支持 pgvector 原生类型)和 dotenv 加载配置。

// demo.go
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "strings"

    "github.com/jackc/pgx/v5"
    "github.com/jackc/pgx/v5/pgxpool"
    "github.com/joho/godotenv"
)

type Item struct {
    Label string
    Vec   []float32
}

func main() {
    // 加载 .env
    if err := godotenv.Load(); err != nil {
        log.Println("No .env file found")
    }

    ctx := context.Background()
    connStr := os.Getenv("DATABASE_URL")
    if connStr == "" {
        connStr = "postgres://user:password@localhost:5432/mydb"
    }

    pool, err := pgxpool.New(ctx, connStr)
    if err != nil {
        log.Fatal("Unable to connect:", err)
    }
    defer pool.Close()

    // 1. 启用扩展
    _, err = pool.Exec(ctx, "CREATE EXTENSION IF NOT EXISTS vector")
    if err != nil {
        log.Fatal("Create extension failed:", err)
    }
    fmt.Println("pgvector extension enabled")

    // 2. 创建表
    _, err = pool.Exec(ctx, "DROP TABLE IF EXISTS items")
    if err != nil {
        log.Fatal("Drop table failed:", err)
    }
    _, err = pool.Exec(ctx, `
        CREATE TABLE items (
            id SERIAL PRIMARY KEY,
            embedding VECTOR(3) NOT NULL,
            label TEXT
        )
    `)
    if err != nil {
        log.Fatal("Create table failed:", err)
    }
    fmt.Println("Table 'items' created")

    // 3. 插入数据
    samples := []Item{
        {Label: "Dog", Vec: []float32{0.9, 0.1, 0.2}},
        {Label: "Cat", Vec: []float32{0.2, 0.8, 0.1}},
        {Label: "Bird", Vec: []float32{0.1, 0.2, 0.9}},
        {Label: "Fish", Vec: []float32{0.3, 0.3, 0.3}},
    }
    for _, s := range samples {
        vecStr := fmt.Sprintf("[%v,%v,%v]", s.Vec[0], s.Vec[1], s.Vec[2])
        _, err = pool.Exec(ctx,
            "INSERT INTO items (embedding, label) VALUES ($1, $2)",
            vecStr, s.Label,
        )
        if err != nil {
            log.Fatal("Insert failed:", err)
        }
    }
    fmt.Printf("Inserted %d items\n", len(samples))

    // 4. L2 距离搜索
    queryVec := []float32{0.8, 0.2, 0.1}
    queryStr := fmt.Sprintf("[%v,%v,%v]", queryVec[0], queryVec[1], queryVec[2])
    rows, err := pool.Query(ctx, `
        SELECT label, embedding <-> $1::vector AS distance
        FROM items
        ORDER BY embedding <-> $1::vector
        LIMIT 3
    `, queryStr)
    if err != nil {
        log.Fatal("L2 query failed:", err)
    }
    defer rows.Close()

    fmt.Println("\n--- L2 Distance Search ---")
    for rows.Next() {
        var label string
        var dist float64
        if err := rows.Scan(&label, &dist); err != nil {
            log.Fatal("Scan failed:", err)
        }
        fmt.Printf("Label: %s, Distance: %.4f\n", label, dist)
    }

    // 5. 余弦距离搜索
    rows2, err := pool.Query(ctx, `
        SELECT label, embedding <=> $1::vector AS cosine_dist
        FROM items
        ORDER BY embedding <=> $1::vector
        LIMIT 3
    `, queryStr)
    if err != nil {
        log.Fatal("Cosine query failed:", err)
    }
    defer rows2.Close()

    fmt.Println("\n--- Cosine Distance Search ---")
    for rows2.Next() {
        var label string
        var dist float64
        if err := rows2.Scan(&label, &dist); err != nil {
            log.Fatal("Scan failed:", err)
        }
        fmt.Printf("Label: %s, Cosine Dist: %.4f\n", label, dist)
    }
}

运行说明:

  • 依赖:github.com/jackc/pgx/v5 和 github.com/joho/godotenv
  • 初始化:go mod init demo && go get github.com/jackc/pgx/v5 github.com/joho/godotenv
  • 启动:go run demo.go

Python 语言示例

使用 psycopg2(或 psycopg 3)和 python-dotenv。

# demo.py
import os
import psycopg2
from psycopg2.extras import execute_values
from dotenv import load_dotenv

load_dotenv()

def main():
    conn_str = os.getenv("DATABASE_URL", "postgresql://user:password@localhost:5432/mydb")
    conn = psycopg2.connect(conn_str)
    conn.autocommit = True
    cur = conn.cursor()

    # 1. 启用扩展
    cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
    print("pgvector extension enabled")

    # 2. 创建表
    cur.execute("DROP TABLE IF EXISTS items")
    cur.execute("""
        CREATE TABLE items (
            id SERIAL PRIMARY KEY,
            embedding VECTOR(3) NOT NULL,
            label TEXT
        )
    """)
    print("Table 'items' created")

    # 3. 插入数据
    samples = [
        ("Dog", [0.9, 0.1, 0.2]),
        ("Cat", [0.2, 0.8, 0.1]),
        ("Bird", [0.1, 0.2, 0.9]),
        ("Fish", [0.3, 0.3, 0.3]),
    ]
    for label, vec in samples:
        cur.execute(
            "INSERT INTO items (embedding, label) VALUES (%s, %s)",
            (f"[{','.join(map(str, vec))}]", label)
        )
    print(f"Inserted {len(samples)} items")

    # 4. L2 距离搜索
    query_vec = [0.8, 0.2, 0.1]
    query_str = f"[{','.join(map(str, query_vec))}]"
    cur.execute("""
        SELECT label, embedding <-> %s::vector AS distance
        FROM items
        ORDER BY embedding <-> %s::vector
        LIMIT 3
    """, (query_str, query_str))
    print("\n--- L2 Distance Search ---")
    for label, dist in cur.fetchall():
        print(f"Label: {label}, Distance: {dist:.4f}")

    # 5. 余弦距离搜索
    cur.execute("""
        SELECT label, embedding <=> %s::vector AS cosine_dist
        FROM items
        ORDER BY embedding <=> %s::vector
        LIMIT 3
    """, (query_str, query_str))
    print("\n--- Cosine Distance Search ---")
    for label, dist in cur.fetchall():
        print(f"Label: {label}, Cosine Dist: {dist:.4f}")

    cur.close()
    conn.close()

if __name__ == "__main__":
    main()

运行说明:

  • 依赖:psycopg2-binary 和 python-dotenv
  • 安装:pip install psycopg2-binary python-dotenv
  • 启动:python demo.py

Java 语言示例

使用 JDBC 驱动(org.postgresql:postgresql)和 dotenv-java。

// Demo.java
import java.sql.*;
import java.util.*;
import io.github.cdimascio.dotenv.Dotenv;

public class Demo {
    public static void main(String[] args) throws SQLException {
        Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
        String url = dotenv.get("DATABASE_URL", "jdbc:postgresql://localhost:5432/mydb?user=user&password=password");
        try (Connection conn = DriverManager.getConnection(url)) {
            // 1. 启用扩展
            try (Statement st = conn.createStatement()) {
                st.execute("CREATE EXTENSION IF NOT EXISTS vector");
                System.out.println("pgvector extension enabled");
            }

            // 2. 创建表
            try (Statement st = conn.createStatement()) {
                st.execute("DROP TABLE IF EXISTS items");
                st.execute("""
                    CREATE TABLE items (
                        id SERIAL PRIMARY KEY,
                        embedding VECTOR(3) NOT NULL,
                        label TEXT
                    )
                """);
                System.out.println("Table 'items' created");
            }

            // 3. 插入数据
            String[][] samples = {
                {"Dog", "[0.9,0.1,0.2]"},
                {"Cat", "[0.2,0.8,0.1]"},
                {"Bird", "[0.1,0.2,0.9]"},
                {"Fish", "[0.3,0.3,0.3]"}
            };
            try (PreparedStatement pst = conn.prepareStatement(
                    "INSERT INTO items (embedding, label) VALUES (?::vector, ?)")) {
                for (String[] s : samples) {
                    pst.setString(1, s[1]);
                    pst.setString(2, s[0]);
                    pst.addBatch();
                }
                pst.executeBatch();
                System.out.println("Inserted " + samples.length + " items");
            }

            String queryVec = "[0.8,0.2,0.1]";

            // 4. L2 距离搜索
            try (PreparedStatement pst = conn.prepareStatement("""
                    SELECT label, embedding <-> ?::vector AS distance
                    FROM items
                    ORDER BY embedding <-> ?::vector
                    LIMIT 3
                """)) {
                pst.setString(1, queryVec);
                pst.setString(2, queryVec);
                ResultSet rs = pst.executeQuery();
                System.out.println("\n--- L2 Distance Search ---");
                while (rs.next()) {
                    System.out.printf("Label: %s, Distance: %.4f%n",
                        rs.getString("label"), rs.getDouble("distance"));
                }
            }

            // 5. 余弦距离搜索
            try (PreparedStatement pst = conn.prepareStatement("""
                    SELECT label, embedding <=> ?::vector AS cosine_dist
                    FROM items
                    ORDER BY embedding <=> ?::vector
                    LIMIT 3
                """)) {
                pst.setString(1, queryVec);
                pst.setString(2, queryVec);
                ResultSet rs = pst.executeQuery();
                System.out.println("\n--- Cosine Distance Search ---");
                while (rs.next()) {
                    System.out.printf("Label: %s, Cosine Dist: %.4f%n",
                        rs.getString("label"), rs.getDouble("cosine_dist"));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

运行说明:

  • 依赖(Maven):
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.7.3</version>
    </dependency>
    <dependency>
        <groupId>io.github.cdimascio</groupId>
        <artifactId>dotenv-java</artifactId>
        <version>3.0.0</version>
    </dependency>
    
  • 编译:javac -cp ".:postgresql-42.7.3.jar:dotenv-java-3.0.0.jar" Demo.java
  • 运行:java -cp ".:postgresql-42.7.3.jar:dotenv-java-3.0.0.jar" Demo

多语言对比

维度Node.js (参考)GoPythonJava
驱动/库pg (npm)pgx/v5psycopg2postgresql JDBC
向量类型处理字符串拼接,由pgvector解析字符串拼接字符串拼接字符串拼接(支持 ?::vector 强制转换)
连接管理Client 对象pgxpool.Poolconnection + 游标Connection + PreparedStatement
批量插入循环单条循环单条(可改用 COPY)循环单条(可改用 execute_values)使用 addBatch() + executeBatch()
参数化查询使用 $1 占位符使用 $1 占位符使用 %s 占位符(需手动构造)使用 ? 占位符
错误处理try-catch 异步if err != niltry-excepttry-catch
环境变量加载dotenvgodotenvpython-dotenvdotenv-java
输出格式console.logfmt.PrintfprintSystem.out.printf
适用版本PostgreSQL 14+ / pgvector 0.5+PostgreSQL 14+ / pgvector 0.5+PostgreSQL 14+ / pgvector 0.5+PostgreSQL 14+ / pgvector 0.5+
性能特点异步非阻塞,适合高并发高并发,连接池内置同步阻塞,适合脚本/中小应用同步阻塞,企业级连接池(需额外配置)

核心共通点:

  • 所有语言均通过字符串拼接生成 vector 字面量(如 [0.1,0.2,0.3]),并依赖 pgvector 的隐式类型转换。
  • SQL 查询逻辑完全一致:使用 <-> 和 <=> 操作符,结合 ORDER BY 和 LIMIT 实现最近邻搜索。
  • 索引创建、扩展启用等 DDL 语句在各类驱动中均以普通 SQL 执行,无特殊差异。

推荐实践:

  • 对于生产级 Go 应用,建议使用 pgx 的 pgtype 自定义类型或 pgvector 官方提供的 Go 类型(github.com/pgvector/pgvector-go)来避免字符串拼接,提升类型安全。
  • Python 可使用 pgvector 官方 Python 包(pgvector)提供 SQLAlchemy 或直接使用 psycopg2 的适配器。
  • Java 可使用 pgvector 提供的 JDBC 类型映射或自定义 ParameterizedType。

项目难点与解决方案

核心难点

  • 高维向量存储与索引构建的内存开销:向量维度通常达数百至数千,索引构建时需大量内存,可能导致OOM
  • 查询精度与速度的权衡:近似索引(IVFFlat/HNSW)需调优参数,否则召回率不达标
  • 与现有关系型数据模型的融合:向量列与传统业务字段(如标签、时间戳)混合查询时的优化

解决方案

  • 内存控制:调整PostgreSQL的 work_mem 和 maintenance_work_mem,对索引构建使用 CREATE INDEX CONCURRENTLY 并设置 max_parallel_workers 限制并行度
  • 参数调优:通过测试集评估不同 lists/probes(IVFFlat)或 m/ef_construction(HNSW)组合下的召回率,使用 pgvector 的 ivfflat.probes 和 hnsw.ef_search 会话级设置动态调整
  • 混合查询:利用PostgreSQL的索引组合能力,为向量列建ANN索引,同时为标量列建B-tree索引,查询时使用 WHERE 先过滤再排序,或采用 ORDER BY embedding <-> query LIMIT 100 后二次过滤

广度

涉及数据库扩展开发、向量索引理论(聚类与图搜索)、嵌入模型集成、性能调优、混合查询优化等多个领域。

深度

深入探讨了 pgvector 的内部实现机制,包括 IVFFlat 的聚类算法、HNSW 的图遍历策略,以及距离度量对搜索结果的影响。

复杂度

向量数据管理涉及数据科学(嵌入生成)、数据库运维(索引调优)、应用开发(API集成)三方面的协同,对团队技术栈要求较高。

官方文档与参考链接

官方文档

参考链接

总结

本文从向量数据的基础概念出发,系统阐述了向量化表示(Embedding)如何将现实世界的事物映射到高维数学空间,并利用距离度量实现语义检索。在此基础上,深入介绍了PostgreSQL的 pgvector 扩展,涵盖其数据类型、距离算子、精确与近似检索的SQL实现,以及 IVFFlat 和 HNSW 两种索引的原理与调优方法。

通过完整的Node.js Demo,展示了从嵌入生成到数据库存储与查询的全链路工程实践。向量数据库作为AI应用的关键基础设施,pgvector 凭借PostgreSQL的生态优势和SQL标准的兼容性,为开发者提供了低成本、高可靠性的向量管理解决方案。

向量数据库:PGVector PGVector 是一个基于 PostgreSQL 的扩展插件,为用户提供了一套强大的向量存储和查询的功能 阅读详情

相关推荐

PostgreSQL向量数据插件--pgvector安装(附PostgreSQL安装)

本文为本人在安装pgvector中踩过的坑,已找到解决方法,现向大家分享。

m0_62627802的博客 2万+

PostgreSQL 支持向量功能

基于 RAG(Retrieval-Augmented Generation)引擎的企业级 AI 业务,对向量的存储和检索是至关重要的,各种类型的数据(文本、图像、声音和视频)都可以转换为向量,从而为复杂的数据结构提供了一种强大的数据表示能力,使其在机器学习和人工智能领域有着广泛的适用性和重要性。操作系统为 Ubuntu os 22.04 64 位操作系统,首先安装数据库、附加组件包(包括一系列扩展模块、工具、插件和额外功能),以及数据库的软件开发包(包括开发工作所需要的头文件和各类函数)。

厉力文武 1259

Postgresql17数据库中通过代码安装向量插件:PGVector0.8.1

pgvector 是一个为 PostgreSQL 数据库开发的开源扩展,它使得 PostgreSQL 能够原生支持向量(Vector)数据类型和向量相似性搜索,是构建 AI 应用(尤其是基于大语言模型 LLM 的应用)的关键基础设施。将向量与其余数据一起存储。支持:1.精确和近似最近邻搜索2.单精度、半精度、二进制和稀疏向量3.L2距离、内积、余弦距离、L1距离、汉明距离和雅克卡德距离4.具有Postgres客户端的任何语言。

xinvictory的专栏 1299

PostgreSQL 向量数据存储指南

向量是一种数学对象,可以表示为一个有序数列。向量数据通常用于表示特征向量、坐标、图像数据、音频数据等。在机器学习、图像处理、自然语言处理等领域,向量数据被广泛应用。PostgreSQL 是一种强大的开源关系型数据库管理系统,以其高扩展性和丰富的功能著称。它支持各种数据类型和高级查询,特别适合处理复杂的数据结构和大规模数据。创建一个名为VectorData// Getters and Setters // toString() 方法 }

繁依Fanyi的博客 2914

PostgreSQL向量化扩展pgvector:原理、优化与应用实践

向量数据库技术通过将非结构化数据(如文本、图像)转换为高维向量,实现了基于语义的相似度搜索。PostgreSQL的pgvector扩展为传统关系型数据库赋予了向量处理能力,支持欧式距离、内积和余弦相似度等多种计算方式。该技术通过IVFFlat和HNSW索引实现高效近似搜索,在电商推荐、异常检测等AI应用场景中展现出强大价值。实际测试表明,在百万级数据规模下,HNSW索引可实现毫秒级响应,同时保持98%的召回率。pgvector的混合查询能力(结合向量搜索与SQL过滤)使其成为需要强一致性的业务场景的理想选择

weixin_33691817的博客 447

PostgreSQL向量化扩展pgvector:原理与应用实践

向量嵌入(Vector Embeddings)作为现代AI系统的核心数据表示方式,能够将文本、图像等非结构化数据转换为高维数值向量,从而捕捉深层次的语义关系。pgvector作为PostgreSQL的扩展插件,通过引入专门的向量数据类型和高效的相似性搜索算法(如IVFFlat和HNSW),使传统关系型数据库具备了处理高维向量的能力。这种技术方案特别适合需要同时处理结构化数据和向量搜索的场景,如推荐系统、语义搜索等。相比专用向量数据库,pgvector的优势在于无缝集成PostgreSQL的ACID事务、复杂

weixin_30764771的博客 305

5步快速部署pgvector:PostgreSQL向量搜索扩展终极指南

你是否正在寻找一个简单高效的向量数据库扩展,让你的PostgreSQL具备AI时代必备的向量搜索能力?pgvector正是你需要的PostgreSQL插件!这个开源向量数据库扩展让PostgreSQL从传统的关系型数据库华丽转身,成为支持向量相似性搜索的强大工具。 想象一下,你的应用程序需要处理海量文本、图像或音频的嵌入向量,传统的关键词搜索已经无法满足精准匹配的需求。pgvector向量数据库

gitblog_00130的博客 908

PostgreSQL 上的向量搜索实践

向量搜索的核心,是将文本、图像或音频等信息转化为向量化的数字表示,并在高维空间中通过计算“距离”来衡量相似度。例如,当一句文本被模型转化为向量后,它可以被表示为一个点,落在三维或更高维的坐标空间中。类似语义的词语(如 student 和 school)会自然地分布在相互接近的位置,当输入一个新词(如 elementary),就可以通过计算向量间的距离,快速找到与之语义相似的词语。数据向量化:无论是文本还是查询请求,都会经过 Embedding Model 转化 Vector Embedding。

IvorySQL的博客 835

PostgreSQL 向量扩展插件pgvector安装和使用

重新修改.bash_profile,配置PG_CONFIG和LD_LIBRARY_PATH,指定安装的postgresql 16.3版本。make编译调用的postgresql不是之前安装的 16.3,而是apt-get 安装的10.23版本。利用pgvector 前提是事先安装好postgresql,安装postgresql例可参考下文。make编译pgvector时无法识别安装的postgresql。安装的版本不是最新的版本而是10.23。pgvector的安装成功。

qw10248的博客 717

《供应链金融风控模型设计:多维度数据整合与动态风险预警机制》

注:方案突出供应链金融「三流合一」(资金流、物流、信息流)的风控逻辑,通过多维度数据整合与动态预警,解决传统风控中中小企业信用评估难、链条风险传导监测滞后的问题。◦ 构建供应链拓扑图,计算「核心企业违约对上下游企业的影响系数」(如某电子厂商违约时,其前五大供应商的风险权重自动提升40%)• 区块链应用:将核心企业应付账款上链存证,防止「一票多融」欺诈(如某供应商用同一应收账款在多家银行融资)◦ 财务数据:核心企业审计报告、上下游企业纳税数据(增值税发票频次/金额)

2501_91651383的博客 1551

【强化学习】常用算法之一 “A3C”

A3C(Asynchronous Advantage Actor-Critic)算法是一种在强化学习领域中应用广泛的算法,它结合了策略梯度方法和价值函数的学习,用于近似解决马尔可夫决策过程(Markov Decision Process)问题。A3C算法在近年来备受关注,因为它在处理大规模连续动作空间和高维状态空间方面具有出色的性能。A3C算法是一种基于策略梯度的强化学习算法,通过多个并行的智能体异步地与环境交互,并利用Actor和Critic网络实现策略和价值的近似,从而实现快速而稳定的强化学习训练。

Code_and516的博客 6759

PostgreSQL 数据库向量化的核心:pgvector

pgvector是一款开源的向量搜索引擎,除了具备所有Postgres数据库的特性外,最主要的特点是能在Postgres数据库存储和检索向量数据,支持向量的精确检索和模糊检索。向量格式除了传统embedding模型的单精度浮点数外,还支持半精度浮点数,二元向量或者稀疏向量。

深度学习机器 2434

STM32H750VBT6在Keil5下载报错?三步搞定Flash Download Failed Cortex-M7问题

本文针对STM32H750VBT6在Keil5开发环境中常见的“Flash Download Failed Cortex-M7”下载报错问题,提供了系统性的三步解决方案。核心在于理解该芯片128KB内部Flash的特殊性,并详细指导用户检查更新Device Pack、精确配置Flash下载算法地址(0x08000000, 0x20000)以及进行硬件连接验证,从而快速修复调试器与芯片间的Flash操作协议问题。

weixin_29274969的博客 481

PostgreSQL pgvector扩展深度解析:向量相似度搜索的技术实现与工程实践

在当今AI驱动的应用场景中,高效处理高维向量数据已成为现代数据库系统的核心挑战。传统的关系型数据库在面对向量相似度搜索时面临性能瓶颈,而专门的向量数据库又带来了额外的系统复杂性和数据同步问题。PostgreSQL pgvector扩展正是为解决这一矛盾而生,它将向量搜索能力无缝集成到成熟的PostgreSQL生态系统中。 ## 向量相似度搜索的核心挑战与技术选型 ### 传统方案的技术瓶颈

gitblog_00251的博客 1075

Ollama教程——模型:如何将模型高效导入到ollama框架

本教程详细介绍了如何将GGUF、PyTorch和Safetensors格式的模型导入到ollama框架中。通过一系列步骤,从环境准备、模型转换和量化,到最终的模型创建和运行,我们为开发者提供了一个清晰的指南。此外,我们还探讨了如何发布模型到ollama平台,使得模型可以被更广泛地分享和应用。ollama框架提供了一个灵活、高效的解决方案,帮助开发者在各种环境中部署和运行机器学习模型。无论您是想将现有模型快速部署到生产环境,还是希望探索新的模型架构,ollama都能为您提供所需的工具和资源。

walkskyer的博客 1万+

如何通过 Python 爬虫抓取并分析政府开放数据

通过本文的介绍,我们展示了如何使用Python 爬虫抓取并分析政府开放数据。通过requests获取政府开放数据的 API 接口数据。使用和pandas进行数据清洗与存储。使用matplotlib对数据进行可视化。你可以根据不同的政府开放平台和数据集进行调整,应用于不同领域的数据分析,如公共健康、交通、环保等。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1079

如何为PostgreSQL添加向量搜索能力:pgvector扩展的完整实战指南

在AI应用爆炸式增长的今天,向量相似性搜索已成为现代数据库系统的核心需求。pgvector作为一个开源的PostgreSQL扩展,为传统关系型数据库赋予了强大的向量搜索能力,让你可以在PostgreSQL中直接存储和查询向量数据,实现高效的近似最近邻搜索。 ## 为什么你需要pgvector?🎯 想象一下这样的场景:你的电商平台需要根据用户浏览历史推荐相似商品,你的内容平台需要根据文章语义匹

gitblog_01427的博客 700

uniapp - 解决苹果ios真机运行报错:错误:请查看是否设备未加入到证书列表或者确认证书类型是否匹配(HbuilderX运行IOS手机出现问题,uniapp运行到IOS真机提示请查看是否设备未)

uni-app,ios苹果,App平台端,uniapp运行到IOS真机提示错误:请查看是否设备未加入到证书列表或者确认证书类型是否匹配,UUID在哪里,uniApp运行到ios真机报错解决,安装自定义基座App失败,没找到有计划资源,证书、标识符和描述文件,证书标识符和描述文件下面没有选项怎么回事,HBuilder真机调试报错请查看是否设备未加入到证书列表或者确认证书类型是否匹配,UniApp运行到IOS真机提示,授权的设备才可以直接安装基座,添加调试的IOS设备,在Devices中,添加手机设备UUID

🏆 前端领域优质创作者 1万+

Windows 下编译安装 pgvector 向量搜索扩展完整实战

`nmake /F Makefile.win` 跑完,弹出 `PGROOT is not set`——这是多数人在 Windows 上编译 pgvector 时遇到的第一个报错。紧接着,`Cannot open include file: 'postgres.h': No such file or directory` 和 `error C2196: case value '4' already

gitblog_00213的博客 955

Altium Designer系列: 软件系统参数配置保存

【Altium Designer实践】 软件系统参数配置保存 作者 CodeAllen ,转载请注明出处 目录设置中文菜单高亮模式设置文件关联pcb设置布线设置保存配置文件(复用) 设置中文菜单 DXP->Preferences 高亮模式设置 文件关联 pcb设置 布线设置 保存配置文件(复用) ......

CodeAllen嵌入式 2019

【图像去噪】论文复现:降维打击!图像对输入变成像素对输入!Pytorch实现Noise2Void(N2V),基于U-Net模型训练,简洁明了理解N2V核心思想!附训练好的灰度图和RGB图的模型文件!

论文题目:Noise2Void-Learning Denoising from Single Noisy Images —— Noise2Void -从单个噪声图像中学习去噪【图像去噪】论文精读:Noise2Void-Learning Denoising from Single Noisy Images(N2V)由于源码是tensorflow版本,本文将基于Pytorch复现。

主要更新底层视觉(去噪、超分等)相关的科研内容,形式为【论文精读】+【论文复现】 1356

PostgreSQL与PGVector:将向量存储和搜索集成到数据库中的革命性技术

随着人工智能和机器学习的迅猛发展,向量化表示(Vectorization)在数据处理中变得越来越重要。特别是在自然语言处理(NLP)、计算机视觉等领域中,向量的使用已经成为了标准。为了在数据库层面上有效地管理这些向量,PGVector应运而生。PGVector是一个PostgreSQL扩展,用于在数据库中存储和搜索向量数据。本文将详细介绍PostgreSQL与PGVector的结合,及其在现代数据处理中的重要性,并探讨PGVector的底层原理及其实际应用。

hello.reader 3693

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

PostgreSQL向量搜索终极指南:pgvector完整入门教程

PostgreSQL向量相似性搜索扩展pgvector为数据库带来了强大的AI能力,让您能够在PostgreSQL中直接存储和搜索向量数据。这个开源项目让开发者能够在熟悉的SQL环境中实现高效的近似最近邻搜索,完美支持机器学习模型生成的嵌入向量。pgvector支持单精度向量、半精度向量、二进制向量和稀疏向量,并提供多种距离度量函数,是构建智能应用的基础设施。 ## 📊 为什么选择pgvect

gitblog_00938的博客 1042

贝叶斯统计 [韦来生编]

贝叶斯(Bayes)统计是近几十年来迅速发展起来的数理统计的一个重要分支。贝叶斯方法与经典统计方法的主要不同之处是进行统计推断时除了利用样本信息外,还要利用参数的先验信息,因此可以提高统计推断或统计决策的效果。它在经济、金融、生物、医学、自然科学和社会科学等许多领域具有广泛的应用。贝叶斯方法的研究已渗透到了统计学的几乎所有领域。作者在给中国科学技术大学概率论与数理统计专业本科生开设的“数理统计”课程中有一章专门讲授“贝叶斯方法和统计决策理论”,近十年来也给中国科学技术大学概率统计专业研究生开设过几次“贝叶斯分析”课。本书是在对过去讲稿的内容作了适当的增减和调整的基础上完成的。  本书共分六章。第1章是绪论,介绍了贝叶斯统计的若干基本概念,同时对必要的数理统计的基础知识有重点地作了回顾。第二章介绍了确定先验分布的若干可供选择的方法。第三章介绍了几类常见统计模型参数的后验分布的主要结果和计算方法。第四章和第五章分别介绍了“贝叶斯统计推断”和“贝叶斯统计决策”的内容。第六章介绍了贝叶斯统计计算的若干方法,包括蒙特卡洛方法和马尔可夫链蒙特卡洛(MCMC)方法以及相关统计软件的简介。本书的每一章附右大量的例题和习题,书末附有常用的几个表格和部分习题解答供读者查用。  大约可在54学时内讲授本书的第1章至第六章的主要内容,教师可根据学时需要选讲本书第1章至第六章的部分内容。书中标“*”号的章节可略去不讲,留给读者作为阅读材料。第六章例题中的R代码和部分例题的数据文件等可从作者个人主页下载。  作者在编写本书的过程中参考了J.O.Berger教授的《统计决策论及贝叶斯分析》和茆诗松教授编写的《贝叶斯统计》等书中的一些内容,以及《贝叶斯分析》一书中由张伟平博士编写的有关贝叶斯统计计算的一些内容。在此表示衷心感谢。  本书编写准备过程中,中国科学技术大学统计与金融系的几个研究生帮助完成了本书部分中文的录入和排版,作者对他们的辛勤工作表示真诚的感谢。高等教育出版社为本书的出版给予了大力支持。在此一并致谢。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

pgvector自然语言处理:文本向量化的应用

在当今AI驱动的时代,文本数据呈爆炸式增长。传统的数据库虽然擅长结构化数据存储,但在处理高维向量数据时却力不从心。你是否曾经遇到过这样的困境: - 需要为海量文本数据构建智能搜索系统? - 希望实现语义相似度匹配而非关键词匹配? - 想要在数据库中直接进行机器学习模型的向量计算? pgvector正是为解决这些问题而生。作为PostgreSQL的开源向量相似度搜索扩展,它将向量数据库的能力直接...

gitblog_01166的博客 929

pgvector模型集成:机器学习模型的向量化存储

在人工智能和机器学习快速发展的今天,如何处理和存储海量的模型嵌入向量(Embedding Vectors)成为了每个技术团队面临的核心挑战。传统的数据库系统在处理高维向量相似性搜索时往往力不从心,而专门的向量数据库又增加了系统复杂度和运维成本。 pgvector作为PostgreSQL的开源扩展,完美解决了这一痛点。它将向量相似性搜索能力直接集成到成熟的关系型数据库中,让您能够在熟悉的SQL环境...

gitblog_00033的博客 1187

pgvector:在PostgreSQL中构建企业级向量数据库的3种核心模式

PostgreSQL作为最强大的开源关系数据库,在AI时代面临着一个关键挑战:如何高效处理高维向量数据?传统的关系型数据库在处理向量相似度搜索时性能瓶颈明显,而专用向量数据库又带来了额外的运维复杂性和数据孤岛问题。pgvector作为PostgreSQL的原生扩展,完美解决了这一矛盾,让你在熟悉的PostgreSQL生态中构建高性能向量搜索能力。 ## 核心理念:为什么选择向量化的关系数据库

gitblog_01146的博客 697
上一篇: PostgreSQL笔记40:PG生态中AI与向量检索扩展全景概览
下一篇: PostgreSQL笔记42: pgvector扩展安装与配置完全指南
Wang's Blog
博客等级 码龄12年 7438粉丝 2422原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值