1. 智能体为什么先得解决“记忆”问题1.1 JchatMind 第二天遇到的真问题JchatMind 是我最近在搭的一个智能体项目昨天把 Agent 的基本流程跑通了用户输入、意图识别、工具调用、结果返回链路已经能转起来。但今天一动手就卡在了一个绕不开的环节——记忆。一台能正常聊天的智能体如果每次会话都从零开始它就不知道自己是谁、不知道用户之前说过什么、也没法在后续对话里引用历史信息。上下文窗口再大也是有限度的用户不可能把所有背景每次重发一遍。所以我需要给 JchatMind 加一个“长期记忆”模块把历史对话、用户偏好、知识片段都存下来在合适的时机把相关内容捞出来塞进当前 prompt。顺着这个需求往下想问题就来了这种“捞出来”的动作应该用什么数据库我先说我踩过的弯路。一开始想着“业务数据不都在 MySQL 里吗顺手也存记忆算了”。数据量小的时候用关键词 LIKE 查一下似乎也够用。但只要对话内容稍微多一点就会发现关键词匹配完全不是那么回事用户说“帮我安排一下下周的评审会”历史记忆里存的是“提醒我周三下午三点有技术评审”这俩句子在字面上没有任何重合可语义上就是同一件事。用 LIKE 查“评审会”大概率什么都查不到。这就是智能体项目的核心痛点我们需要的是语义检索不是字符匹配。而语义检索的常规做法是把文本用 embedding 模型转成高维向量存进数据库再用向量距离去查“最相似的内容”。所以选型的关键就变成了哪个数据库能既帮我管好业务数据又能高效地做向量检索。1.2 向量检索到底是在解决什么问题先聊清楚向量是什么不然后面选型判断全是空的。一个 embedding 模型可以把一句话变成一串浮点数比如 768 维或 1536 维的数组。这个数组不是随便生成的它的空间位置代表了这句话的语义。两句话如果语义接近它们在多维空间里的距离就小语义无关距离就大。检索时拿用户的 query 向量去和库里所有向量比距离返回最近的 Top N就是“语义检索”。这个过程专业术语叫 ANN近似最近邻搜索专门解决“在几十万、几百万条向量里快速找到最相似的几条”的问题。传统数据库的 B 树索引在这件事上帮不上忙因为它只适合做精确匹配和范围查询。MySQL 在跑 LIKE 时那叫一个慢跟向量检索更不沾边——它没有原生的向量类型也没有对应的索引算法。生活化类比关键词搜索像在字典里按笔画机械地找字向量检索像朋友之间聊天“你懂我意思吧”那种只可意会的默契。智能体想要的是后者。所以我现在面临的真正问题是要在关系数据库的 MySQL 和 PostgreSQL 之间做取舍同时要考虑是给 MySQL 硬塞一个向量方案还是让 PostgreSQL 用原生扩展 pgvector 一步到位。1.3 选型候选MySQL 硬刚还是 PG 加插件我先快速过了一眼市场上可用的方案大致三拨继续用 MySQL自己写向量距离计算或者用 UDF、存二进制 blob 硬算。搜索范围小还行一旦数据上了十万条全表扫一遍算余弦距离性能基本没法看。引入专用向量数据库比如 Milvus、Qdrant、Weaviate。功能很专业但等于在项目里多维护一套独立服务还要处理向量库和 MySQL 之间的数据同步架构和运维复杂度立刻翻倍。换用 PostgreSQL pgvector把业务数据和向量数据放在同一个数据库里一个 SQL 就能同时支持业务条件过滤和向量排序。我本身对 MySQL 不陌生很多人都是这么一路用过来的。但回到 JchatMind 的需求本质智能体项目刚起步数据量大概率是十万到百万级团队规模小能少维护一个组件就少维护一个。MySQL 没有原生向量能力硬上等于所有检索逻辑都要自己造轮子专用向量库虽然是“专业选手”但对一个早期项目来说太重了。反而是 PostgreSQL pgvector既有关系数据库的严谨又能补上向量检索这一课。我选这条路的另一个原因是“把鸡蛋尽量放在一个篮子里”。用 MySQL 存业务数据、再搭一个向量库存 embedding两边的事务、备份、一致性都得分开来操心。PG 一个实例全搞定数据备份恢复都是同一套机制这对一个只有一两台服务器的智能体项目太友好了。2. pgvector 和 MySQL 的硬核差异在哪里2.1 数据结构与扩展能力MySQL 输在起点看一个数据库能不能做向量检索第一反应是看它有没有向量类型。pgvector 为 PostgreSQL 提供了一个名为vector的类型可以声明维度比如embedding vector(768)。建表时就能固定向量维度写入时会自动校验长度代码里就算维度传错了也会被数据库拦下来而不是等到算距离时才出诡异结果。MySQL 这边没有对应的原生向量类型。你想存向量常见做法是存成 JSON、BLOB 或者用逗号分隔的字符串。存进去倒是容易查的时候就尴尬了数据库根本不理解这串数字是向量排序、筛选、计算距离全得靠应用层自己写代码。数据量小感觉不到一旦开始做分页、批量比较或者并发检索代码复杂度和性能瓶颈一起找上门。除了向量SQL 能力也得看。PostgreSQL 的 JSONB、数组、Range 类型比 MySQL 的 JSON 强得多尤其是 JSONB 配合 GIN 索引很适合存 Agent 的 miscellaneous 元数据——比如记忆的来源、时间、标签、对话 ID。我截一段在建表时的感受POSTGRES 可以把metadata JSONB和向量字段放同一行查询时可以先用 SQL 过滤 metadata再在过滤结果集上做向量排序。这么顺滑的混合查询在 MySQL 里基本不敢想。结论不是 MySQL 不好用是它在这个场景下根本没有原生武器。向量检索能力不是靠 SQL 写的技巧能补出来的。2.2 pgvector 的索引算法HNSW 更适合智能体pgvector 目前提供两类索引IVFFlat 和 HNSW。IVFFlat 的原理是先对全量向量做聚类把空间分成多个区域查询时只在最近的几个区域里搜索。建索引快、内存省但有个前提必须先训练聚类中心而且训练数据要能代表真实分布。如果数据量在涨、聚类参数没跟上召回率会明显下降。HNSW 是基于图的算法把向量组织成分层的小世界网络查询时从顶层开始逐层向下搜索。它不需要训练阶段插入即建索引召回率高查询速度稳定代价是内存占用更高、构建索引时间更长。对 JchatMind 这种十万级、百万级的数据规模我的建议是无脑选 HNSW。理由很简单智能体的记忆检索最怕“该想起的没想起”召回率比建索引的几分钟开销重要得多。索引三级参数我一般这么配m 16 -- 每个节点的最大连接数越大召回越好内存也越高 ef_construction 64 -- 建索引时的搜索宽度影响索引质量 ef_search 40 或 64 -- 查询时的搜索宽度大一点召回更高但费 CPU说实话这三个参数在文档里都写了但实际踩坑的人才知道ef_search 太大会让单次查询延迟明显上升。我先用 40 起步后续按查询延迟和召回率调没有魔改的必要。2.3 混合查询一个 SQL 才是王道智能体检索记忆的典型场景长这样只查某个用户的数据、只看最近 7 天的记录、过滤掉失效的知识再按语义相似度排序。这种“业务过滤 向量排序”的混合查询pgvector 可以直接一个 SQL 写完SELECT content, 1 - (embedding $1) AS similarity FROM memories WHERE user_id u_123 AND created_at now() - interval 7 days AND metadata-type preference ORDER BY embedding $1 LIMIT 5;这里的是余弦距离“1 - 距离”就是相似度。查询时先按 SQL 条件把结果集缩小再对剩余向量做排序。MySQL 要做同样的事只能先根据条件从表里查出所有候选再在应用层算出每个向量和 query 的距离最后手动排序取前 N。听起来也不是不能做数据一上万性能和代码复杂度全崩了。2.4 一个表把差异说透我整理了一张速查表方便大家在技术评审或自测时直接对照能力项PostgreSQL pgvectorMySQL原生向量类型vector(n)维度强校验无需自存 JSON/BLOB向量距离算子L2、内积、余弦距离一站式需自写函数ANN 索引HNSW、IVFFlat无语义检索性能十万级毫秒返回全表扫描不可控混合查询过滤 向量排序单条 SQL 完成应用层拼装全文检索能力tsvector / tsquery强内置全文索引可用但一般JSON 及元数据JSONB GIN灵活JSON 虚拟列弱一些事务/一致性MVCCACID 强也支持 ACID事务能力不弱扩展生态插件机制丰富pgvector 活跃扩展相对少这张表不是用来踩 MySQL 的而是明确对比“适不适合智能体项目”。MySQL 在通用 OLTP 场景依然优秀但在向量能力这个维度上pgvector 的优势是碾压性的。3. JchatMind 落地 PostgreSQL pgvector 的实操过程3.1 安装这一步我踩过的最大的坑是编译如果你平时用的是 Windows最简单的办法是直接装 Docker 版的带 pgvector 镜像。省去编译这一步真正推荐给想快速跑通的朋友docker run -d \ --name jchat-pg \ -e POSTGRES_PASSWORDyour_password \ -e POSTGRES_DBjchat \ -p 5432:5432 \ pgvector/pgvector:pg16pgvector/pgvector:pg16这个镜像已经把扩展编译好了创建容器后就自带 vector 扩展。Linux 上如果不想用 Docker走源码编译也顺理成章先确保系统里已经安装了 PostgreSQL 的开发头文件postgresql-server-dev-16这样的包再执行make make install。没有开发头文件的话会报fatal error: postgres.h: No such file or directory。macOS 用 Homebrew 最省事brew install pgvector它会自动识别当前已安装的 PostgreSQL 版本并安装扩展文件。提示Windows 上也可以用安装包自带的pg_config去编译 pgvector但对新手不太友好。我建议先用 Docker 跑通逻辑等需要上生产再考虑原生安装。启用扩展本身很简单CREATE EXTENSION IF NOT EXISTS vector;这条命令执行完PG 才知道vector这个类型存在否则建表时直接报type vector does not exist。3.2 建表、索引与 embedding 维度设计我用的是 768 维的本地 embedding 模型所以建表语句长这样CREATE TABLE memories ( id BIGSERIAL PRIMARY KEY, user_id TEXT NOT NULL DEFAULT , content TEXT NOT NULL, metadata JSONB NOT NULL DEFAULT {}, embedding vector(768) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX ON memories USING hnsw (embedding vector_cosine_ops);最后一行创建的是 HNSW 索引vector_cosine_ops表示这个索引按余弦距离优化。如果你的相似度计算用的是 L2 距离就换vector_l2_ops用内积则用vector_ip_ops。这里多说一句必须保证写入的 embedding 维度和建表维度一致。我在测试时曾经用 1024 维的模型插了一次PG 直接拒绝写入报错提示维度不匹配。这其实是好事比 MySQL 那种“什么都能塞进去最后全乱掉”的隐性问题强得多。还有个小技巧如果一张表里既有普通条件的过滤又要做向量检索可以在 metadata 字段上再加一个 GIN 索引CREATE INDEX ON memories USING gin (metadata);让混合查询的过滤步骤也走索引而不是先拉回全表向量再逐个过滤。3.3 写入和查询Python 示例代码实际编码我用的是 Python psycopg 3写入和查询代码如下import psycopg from your_embedding_module import embed_text # 你的 embedding 函数 conn_info hostlocalhost port5432 dbnamejchat userjchat passwordyour_password # 写入一条记忆 with psycopg.connect(conn_info) as conn: text 用户说周三下午三点有技术评审 emb embed_text(text) # 长度 768 的 list conn.execute( INSERT INTO memories (user_id, content, metadata, embedding) VALUES (%s, %s, %s, %s) , (u_123, text, {source: chat_log}, emb), ) conn.commit() # 查询语义相似内容 with psycopg.connect(conn_info) as conn: query 最近有没有提到开会时间 q_emb embed_text(query) rows conn.execute( SELECT content, 1 - (embedding %s) AS similarity FROM memories WHERE user_id u_123 ORDER BY embedding %s LIMIT 5 , (q_emb, q_emb), ).fetchall() for row in rows: print(f相似度 {row[1]:.4f} {row[0]})注意参数化的写法两个%s都传同一个q_emb就行。embedding %s是在 SQL 里完成向量距离计算的不需要把整个库拉回 Python。检索这套逻辑就是 JchatMind “记忆”功能的核心骨架。后续你再接上下文组装、prompt 拼接都建立在它之上。3.4 从 MySQL 迁移数据时我怎么处理如果你已经在 MySQL 里存了业务数据这块的经验更重要。我在 JchatMind 项目里原本有一张users表和conversations表迁移时并没有直接“双写”长期并行。更稳妥的做法是先建 PG 库把 MySQL 的表结构映射成 PG 的对应类型比如JSON改JSONBVARCHAR按需改TEXT自增主键改BIGSERIAL。然后写一次性同步脚本把 MySQL 的历史数据通过批量查询导入 PG。向量数据不存在“从 MySQL 迁移”的问题因为 MySQL 里本来就没有向量字段。迁移过程里我们做的是“离线重算”把所有历史对话文本重新过一遍 embedding 模型生成向量后和业务数据一起写入 PG。这一步我推荐开多线程分批处理一次处理 1000 条对话写一批再处理下一批。重算完成后在业务侧把数据源从 MySQL 切到 PG然后观察几天日志确认没有异常后再考虑把 MySQL 节点下线或转为只读备份。整个过程最核心的原则是切换不改逻辑只改数据源连接。业务代码里的 SQL 语句如果涉及向量检索从一开始就直接按 PG 语法写避免在 MySQL 里先写了半套再临时改。4. 常见问题与避坑实录4.1 pgvector 安装编译失败的应对编译安装 pgvector最典型的问题除了缺开发头文件还有一个是 PostgreSQL 版本与扩展不兼容。我试过在一台 PG 16 的机器上误下载了适配旧版本的源码make install时直接报错 “server version mismatch”。处理办法很简单下载对应版本的源码包或者用 git 分支切到PG16对应的标签。Docker 场景就更省心了直接换pgvector/pgvector:pg16镜像版本匹配问题全自动。如果是在云上托管数据库很多云厂商现在也直接提供ivfflat/hnsw索引能力不用自己装扩展开通即可用。查到你的云数据库是否默认带vector扩展执行CREATE EXTENSION vector;就能验证。4.2 HNSW 索引没生效才是查询慢的真凶很多人以为装了 HNSW 索引就万事大吉结果查询仍然慢得离谱。这时候第一件事是看执行计划EXPLAIN SELECT content FROM memories ORDER BY embedding $1 LIMIT 5;如果结果里出现Seq Scan on memories说明索引根本没有被使用。常见原因有几种没建对索引类型比如建了ivfflat却用了vector_cosine_ops的算子或者反向。表数据太少优化器认为全表扫描比走索引更快这在小数据量下反而是好事不用太担心。LIMIT后面的数值太小比如LIMIT 1优化器可能觉得直接扫描更省事。还有一次我在 HNSW 索引建完后忘了跑ANALYZE统计信息不更新也会导致执行计划错乱。记下来建索引后顺手执行ANALYZE memories;。4.3 混合过滤时向量检索效果的抖动项目里最常见的检索 query 是“查这个用户最近一周关于会议的记忆”。如果业务过滤几乎筛选不出几条数据比如某个用户只发了 3 条记忆那向量排序就只能在 3 条里做“语义相似度”的意义被严重削弱。踩了这个坑后我的应对策略是先宽召回再精确过滤先不考虑 metadata 里的严格条件用向量检索召回 Top 100然后在应用层过滤user_id、时间等条件再取前 5 条。这么做的好处是召回率大幅提升坏处是可能捞回来的 100 条里一半是无关的但应用层过滤成本极低。不过也存在另一种相反的情形如果 metadata 条件选择性很高比如只查某种特定类型的记忆那你更应该先用 SQL 过滤 metadata再在过滤结果上向量排序别让 HNSW 做无谓的大规模搜索。判断依据是“条件能选出多少比例的数据”比例高就先过滤比例低就先召回。4.4 复盘我最后为什么没选 MySQL选型不是在“哪个数据库更好”上纠结而是看哪个数据库更适合“智能体这个场景”。MySQL 很稳我很尊敬它但在这个项目里它缺的恰恰是最关键的“向量检索”和“语义搜索”能力。硬要改造它等于自己造数据库扩展轮子而不是在解决业务问题。我复盘一下当时的判断路径先想清楚核心需求智能体要“语义记忆”必须用 embedding ANN。再看现有设施已有 MySQL 实例但无法承载向量场景。排除引入专用向量库的原因一个早期智能体项目没必要为了“收藏”多维护一套中间件把事务、备份、监控全拆散。最终选 PostgreSQL pgvector业务数据与向量数据同库一个 SQL 干完活备份恢复和事务性能天然统一。当然如果以后 JchatMind 成长到亿级向量规模或者要做非常复杂的高并发向量检索我可能会调整架构把向量部分拆到 Milvus 或 Qdrant 里。但那一天到来之前PostgreSQL pgvector 的量级和灵活性已经足够支撑项目的演进。个人体会是选型这事没有永远的最优解只有当前阶段最顺手、最能解决实际问题的方案。PG pgvector 对我来说解决了 95% 的问题剩下 5% 的毛病留给未来需要时再处理。如果你也在做智能体项目正纠结于 MySQL 还是 PostgreSQL不妨以“向量检索是否为刚需”作为决策的第一道分水岭答案会清晰很多。