码仔
发布于 2026-03-06 / 4 阅读
0
0

本地搭建 PostgreSQL + PGvector 向量数据库

Docker 快速安装 PostgreSQL + PGvector

我们直接使用官方封装好的镜像,一步到位。打开终端,直接复制并运行下面这段命令(请注意修改 -v 挂载目录为你本地的实际路径):

docker run -d \

  --name my_pgvector \

  -p 5432:5432 \

  -e POSTGRES_PASSWORD=123456 \

  -v ./postgresql/data:/var/lib/postgresql/data \

  pgvector/pgvector:pg17

这条命令会在后台启动一个带有向量扩展(pgvector)的 PostgreSQL 17 数据库容器。

下面对命令进行拆分解释:

  1. docker run -d: - docker run是 Docker 最常用的命令,用于创建一个新的容器并运行一个命令。 - -d (detached): 意思是“后台运行”。如果不加这个参数,容器的运行日志会直接占据你的终端窗口,一旦你按下 Ctrl+C 或者关闭终端,容器通常会停止。加上 -d 后,容器会在后台静默运行。

  2. --name my_pgvector: + 为你的容器指定一个自定义名称叫 my_pgvector。方便后续管理。 + 如果没有这个参数,Docker 会随机分配一个像 determined_byd 这样古怪的名字。有了名字后,你可以直接通过 docker stop my_pgvector 来停止它。

  3. -p 5432:5432: - 端口映射,格式为 宿主机端口:容器内端口。 - PostgreSQL 默认在容器内部监听 5432 端口。通过这个映射,你可以通过访问你电脑(宿主机)的 localhost:5432 来连接这个数据库。

  4. -e POSTGRES_PASSWORD=123456:设置默认超级用户 postgres 的登录密码。在生产环境下,千万不要用 123456 这么简单的密码,建议使用复杂的随机字符串。.

  5. -v ./postgresql/data:/var/lib/postgresql/data(重要!!!)
    - 挂载数据卷(Volume),格式为 宿主机绝对路径:容器内路径
    - 这是最关键的部分,用于 数据持久化 。
    - 容器是一个“临时”的环境,删除容器会丢失内部所有数据。通过这个映射,数据库产生的所有数据都会实时写入到你的磁盘。即使你删除了容器,下次再挂载这个目录,数据依然存在。

  6. pgvector/pgvector:pg17:指定使用的镜像名和标签(Tag)。

PGvector 向量功能

虽然镜像已经内置了向量功能,但 PostgreSQL 的设计是“插件按需加载”。按理说你得手动执行 CREATE EXTENSION vector;

但是! 如果你用的是 Spring AI,你会发现哪怕不手动输入命令,项目也能跑。这是因为 Spring AI 的 PgVectorStore 已经在底层帮你把脏活累活干了。在 Spring 容器启动时,自动检测并初始化 PostgreSQL 的向量数据库环境。

/**
 * PgVectorStore 实现了 InitializingBean 接口。
 * Spring 容器在完成 Bean 的属性注入后,会自动调用 afterPropertiesSet() 方法。
 */
public class PgVectorStore extends AbstractObservationVectorStore implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        // 1. 打印初始化日志,确认当前操作的表名和 Schema 名
        logger.info("Initializing PGVectorStore schema for table: {} in schema: {}", 
                this.getVectorTableName(), this.getSchemaName());

        logger.info("vectorTableValidationsEnabled {}", this.schemaValidation);

        // 2. 校验逻辑:如果开启了校验,则检查数据库中现有的表结构是否符合要求
        if (this.schemaValidation) {
            this.schemaValidator.validateTableSchema(this.getSchemaName(), this.getVectorTableName());
        }

        // 3. 初始化开关:如果 initializeSchema 为 false,则跳过后续的所有建表和安装插件操作
        // 这通常用于生产环境,由 DBA 手动预先创建好环境
        if (!this.initializeSchema) {
            logger.debug("Skipping the schema initialization for the table: {}", this.getFullyQualifiedTableName());
            return;
        }

        // --- 数据库底层环境准备 ---

        // 4. 安装 pgvector 插件:这是实现向量检索的核心扩展
        this.jdbcTemplate.execute("CREATE EXTENSION IF NOT EXISTS vector");
        
        // 5. 安装 hstore 插件:用于支持键值对存储(通常作为 metadata 的补充)
        this.jdbcTemplate.execute("CREATE EXTENSION IF NOT EXISTS hstore");

        // 6. 安装 UUID 插件:如果主键类型配置为 UUID,则需要安装此扩展来生成随机 ID
        if (this.idType == PgIdType.UUID) {
            this.jdbcTemplate.execute("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"");
        }

        // 7. 创建数据库 Schema(命名空间),默认为 "public"
        this.jdbcTemplate.execute(String.format("CREATE SCHEMA IF NOT EXISTS %s", this.getSchemaName()));

        // --- 表结构处理 ---

        // 8. 危险操作:如果配置了 removeExistingVectorStoreTable 为 true,则先删除旧表
        // 警告:这会导致数据丢失,通常仅用于集成测试环境
        if (this.removeExistingVectorStoreTable) {
            this.jdbcTemplate.execute(String.format("DROP TABLE IF EXISTS %s", this.getFullyQualifiedTableName()));
        }

        // 9. 创建向量存储核心表
        // id: 主键,类型根据 getColumnTypeName() 动态决定(如 uuid, serial 等)
        // content: 原始文本内容
        // metadata: 关联的元数据,存储为 JSON 格式
        // embedding: 向量字段,指定维度 vector(N)
        this.jdbcTemplate.execute(String.format("""
                CREATE TABLE IF NOT EXISTS %s (
                    id %s PRIMARY KEY,
                    content text,
                    metadata json,
                    embedding vector(%d)
                )
                """, 
                this.getFullyQualifiedTableName(), 
                this.getColumnTypeName(), 
                this.embeddingDimensions()));

        // --- 性能优化:索引 ---

        // 10. 创建向量索引:提高相似度搜索的效率
        // 如果 createIndexMethod 不是 NONE(如 HNSW 或 IVFFlat),则执行创建索引语句
        if (this.createIndexMethod != PgIndexType.NONE) {
            this.jdbcTemplate.execute(String.format("""
                    CREATE INDEX IF NOT EXISTS %s ON %s USING %s (embedding %s)
                    """, 
                    this.getVectorIndexName(),          // 索引名称
                    this.getFullyQualifiedTableName(),  // 表名
                    this.createIndexMethod,             // 索引算法 (如 hnsw)
                    this.getDistanceType().index        // 距离度量函数 (如 vector_cosine_ops)
            ));
        }
    }
    
    // ... 其他方法
}

不过,需要注意的是:当 initialize-schema: false 时,Spring AI 不会自动创建 vector_store 表(上面源码中我也注释了相应的代码)。

spring:
  ai:
    vectorstore:
      pgvector:
        initialize-schema: true 

建议开发环境设置为 true,方便快速启动。生产环境设置为 false,手动管理数据库 schema,避免意外变更。


评论