PostgreSQL:AI 时代原生的"隐型"向量数据库 — 用 pgvector 给消息表加上长期记忆

PostgreSQL:AI 时代原生的"隐型"向量数据库 --- 用 pgvector 给消息表加上长期记忆

关系型数据库是互联网应用的基石。账号信息、订单数据、聊天记录、业务数据,几乎都依赖它存储。 但当 AI Agent 需要"回忆" 时,传统关系型数据库突然"不够用"了------直到 pgvector 出现。


一、痛点:AI Agent 需要一个"有记忆"的消息表

豆包、Codex 这类 AI Agent,几乎都要长期存储聊天记录。

数据表怎么设计?这是经典的三张表结构:

scss 复制代码
用户表 users
   ├── user_id    主键
会话表 conversations        ── 一对多 ──◎ users
   ├── conversation_id
   ├── title
   └── user_id (关联用户表)
消息表 messages              ── 一对多 ──◎ conversations
   ├── message_id
   ├── content
   └── conversation_id (关联会话表)

SQL 就能解决传统查询:

sql 复制代码
-- 拿到某用户的所有会话
SELECT * FROM conversations WHERE user_id = '你的用户ID';

-- 拿到某会话的所有消息(按时间升序)
SELECT * FROM messages
WHERE conversation_id = '你的会话ID'
ORDER BY created_at ASC;

但问题来了:"按时间找消息" 很简单,"按语义找消息" 怎么做?

比如用户 3 天前说过一句"我把路由器放客厅了",今天问"我把网线放哪了?"------关键词完全不一样,但语义相关。这就要 embedding + 向量检索,普通 SQL 干不了。


二、常规方案:MySQL + Milvus,和它的烦恼

传统上你会引入专门向量数据库(如 Milvus):

复制代码
MySQL(业务数据) + Milvus(向量数据) = 双库双写

麻烦在于:

  • ❌ 双写:插入一条消息,要同时写 MySQL 和 Milvus,事务难保证
  • ❌ 双系统维护:两套部署、两套监控、两套备份
  • ❌ 关联查询撕裂:业务关系和向量检索是分离的,跨库 JOIN 做不了

一句话:Mysql + Milvus = Pg,用一个 PostgreSQL 全搞定。


三、pgvector:给普通列加一个"向量字段"

PostgreSQL + pgvector 扩展的最大优势,就是在原来的消息表上,多加一个向量字段:

graph LR A[MySQL/Milvus 双库] -->|多一个向量列, 一块搞定| B[PostgreSQL + pgvector] B --> C[不用双写] B --> D[不用维护两套系统] B --> E[关系查询 + 向量检索 同一张表]

在 psql_test 里,这一行就是把向量塞进普通表:

js 复制代码
// messages.mjs - 创建消息时写入 embedding
// 把 OpenAI embedding 结果以 $4::vector 写入 PostgreSQL vector 字段
const { rows } = await query(
  `INSERT INTO messages (conversation_id, role, content, embedding, created_at)
   VALUES ($1, $2, $3, $4::vector, $5)
   RETURNING *`,
  [conversationId, role, content, JSON.stringify(vector), new Date().toISOString()]
);

字段类型是 vector,但它在的还是同一张 messages 表。所以你可以------


四、一张表同时做"关系查询 + 语义检索"

这是 pgvector 最惊艳的地方:不用拆分架构、不用同步数据、不用写复杂的关联逻辑。

一条 SQL 完成 4 层过滤:

sql 复制代码
SELECT m.*
FROM messages m
JOIN conversations c ON m.conversation_id = c.id
WHERE
  c.user_id = '你的用户id'     -- ① 按用户过滤
  AND c.id   = '你的会话ID'    -- ② 按会话过滤
ORDER BY
  m.embedding <=> '[1.2, 0.5, 0.8, ....]'  -- ③ 按语义检索
LIMIT 5;                        -- ④ 取最相似 5 条

语义检索的完整实现(psql_test 里真正跑通的代码):

js 复制代码
async function searchSimilarMessages(conversationId, searchText, limit = 5) {
  const vector = await getEmbeddings().embedQuery(searchText);

  const { rows } = await query(
    `SELECT id, conversation_id, role, content, created_at,
            1 - (embedding <=> $1::vector) AS similarity
     FROM messages
     WHERE conversation_id = $2 AND embedding IS NOT NULL
     ORDER BY embedding <=> $1::vector
     LIMIT $3`,
    [JSON.stringify(vector), conversationId, limit]
  );
  return rows;
}

关键就是那个 <=> 运算符。


五、<=> 到底是什么?

这是 pgvector 里的向量余弦距离运算符:

复制代码
向量余弦距离 = 1 - 向量余弦
运算符 含义 用途
<=> 余弦距离 语义相似度,最常用
<-> 欧氏距离 几何距离
<#> 点积 内积相似度

所以 ORDER BY embedding <=> '[0.1,0.2,...]' 就是把某条消息和查询向量余弦距离从小到大排------距离越小越相似。

而 1 - (embedding <=> $1) 则把"距离"转成"相似度",越大越相关。这一条 SQL,就把 AI 最需要的能力------按用户过滤、按会话过滤、按时间过滤、按语义检索------全包含了。

这是单靠 MySQL 做不到的:MySQL 不支持 vector 类型,也就没有 <=> 运算符。


六、工程化:从手写 SQL 到 TypeORM

psql 目录下还有一个更工程化的版本 typeorm-pg-crud ------ 用 NestJS + TypeORM 抹平手写 SQL。

架构路径

rust 复制代码
sql -> ORM -> psql

开发不写 SQL,用 ORM 操作数据库。TypeORM 是 Node 生态主流 ORM,NestJS 的两大特色是 MVC 模块化 和 依赖注入。

实体:SQL 表在 TypeScript 世界的映射

messages 表就映射成一个类,embedding 映射成 vector 类型:

ts 复制代码
// message.entity.ts
@Entity('messages')
export class Message {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ type: 'text' })
  content: string;

  @Column({ type: 'vector', nullable: true })
  embedding: number[] | null;   // ← pgvector 列在 TypeORM 里的映射
}

ORM 操作流程一览(对应 NestJS 的资源模块)

  1. 数据库全局配置 --- TypeOrmModule.forRoot({ type: 'postgres', ... })
  2. 生成资源模块 --- nest g res conversations --no-spec,自动创建 RESTful CRUD
  3. MVC 模块化 --- module 声明 / controller(装饰器路由)/ service(数据操作)
  4. entities --- 实体类,表的映射,需在模块全局注册
  5. dto --- Data Transfer Object,前端 params / query 的提交规则约束,不对就直接报错退出

专业提示:TypeORM 实体涉及 循环依赖 + ESM 时,装饰器用实体名字符串 (@ManyToOne('User', 'conversations'))+ 属性用 Relation<T> + import type 导入,才能既打破编译期循环又满足 isolatedModules 要求。


七、总结

对比项 MySQL + Milvus PostgreSQL + pgvector
向量存储 需额外向量库 普通表加一列
数据写入 双写两套 单表写入
部署维护 两套系统 一套搞定
关系+语义查询 跨库做不了 一条 SQL 搞定
关键词 需要分词/倒排 <=> 余弦距离

MySQL 没向量列、而 PG 有 ------ 这就是 AI 时代,PostgreSQL 被称为"最适合的数据库"的根本原因:

不需要额外的数据库,不需要双写,不需要维护两套系统------一张表,搞定传统关系查询 + AI 长期记忆。

如果你想给 Agent 造一个有长期记忆的后端,不妨把 psql_test 里的 searchSimilarMessages 跑起来,感受一下 "隐型向量数据库" 的威力。

相关推荐
Yuzhiyuxia9 小时前
【AI Agent面试系列】AI Agent可观测、评测、安全相关的知识点
人工智能·安全·面试
2501_933923259 小时前
解读Redis:单线程模型
数据库·redis·缓存
dozenyaoyida10 小时前
AI与大模型新闻日报 | 2026-11-11
人工智能·chatgpt·大模型·新闻
xsd2024111810 小时前
把品牌官网做成一个 3D 世界:scroll-world
人工智能
孟健10 小时前
我用Claude Skills把出海视频月账单从$86打到了$16
人工智能·ai编程·claude
泯泷10 小时前
同样是多 Agent,为什么有的稳,有的却天天翻车?
人工智能·llm·agent
一起学安全技术10 小时前
拆大模型 06|Prompt 工程不靠玄学:上下文要当程序写
人工智能·语言模型·prompt·aigc
鸿蒙开发爱好者11 小时前
下班后做的小工具和小游戏,我把它们整理成了 WyanHub
网络·人工智能·职场和发展·个人开发
Tapdata11 小时前
被忽略的第三类上下文:Agent 进入真实业务后,还缺什么?
数据库·人工智能·架构
地平线智能驾驶开发者11 小时前
征程6|用 hrt_model_exec 看清 YOLOv5x 的输入、原始输出和后处理
人工智能·yolov5·机器视觉