三种数据库会给你三种不同的脸色
有一个问题困扰了我很久:让一个 Agent 同时操作 MongoDB、Redis、Elasticsearch,最难的是什么?

最早我以为是语法。MongoDB 的聚合管道层层嵌套,Elasticsearch 的 DSL 比 JSON 还 JSON,Redis 命令虽然简洁但数量多到记不住。让 LLM 背下这些,总得费一番功夫吧。
后来我在真实项目里跑了三个月,发现自己想反了。语法从来不是瓶颈——你给 LLM 一份 API 文档,它生成什么查询都不费劲。真正的差异藏在更深处:这三种数据库要求 Agent 用三种完全不同的方式思考数据。这篇文章就讲我想明白的东西。
MongoDB:Agent 的任务是描述文档的形状
MongoDB 的数据模型是文档,也就是嵌套的 BSON/JSON。你想查数据,得把一个「过滤器」传给 find(),过滤器本身也是 JSON。
拿「昨天加了购物车但没下单」这个需求举例,查询长这样:
db.cart_items.find({
user_id: userId,
added_at: { $gte: ISODate('2025-02-01T00:00:00Z') },
status: { $nin: ['ordered', 'removed'] }
})
这其实不是「查询」,是对目标文档形状的描述:它应该有 user_id 字段且值等于某个 ID;added_at 字段得大于某个时间点;status 不能是那两个值。Agent 要做的,是把自然语言翻译成这种结构描述——LLM 出奇地擅长这件事,因为自然语言本身就是关于结构的描述。
真正有门槛的是聚合管道。当需求复杂到要分组、排序、展开数组时,查询不再是描述形状,而是设计一条流水线:$match 筛掉不想要的,$unwind 把数组拆开,$group 做聚合,$sort 排个序。每个阶段改变数据形态,喂给下一个阶段。
有意思的是,聚合管道的分步结构与 Agent 的「逐步推理」天然匹配。我在项目里发现,让 LLM 先写一段注释,把每个 stage 的意图列成计划,再生成完整管道,成功率能提高一大截。
还有一个特性值得注意:MongoDB 有 schema,字段可以嵌套,Agent 可以通过查看样本文档(findOne())理解数据形状,再对着形状写查询。它在跟结构化程度很高的数据打交道,核心能力是结构感知。
Redis:命令不是难点,key 才是
到了 Redis 这边,画风突变。
Redis 的命令集很小,语法极简:GET、SET、EXPIRE、ZADD……任何 Agent 学一遍就会。但真正让它卡住的不是命令,而是:它不知道 key 叫什么。
你让 Agent「查一下用户 123 的购物车」,它该读哪个 key?是 cart:123,还是 cart:user:123,还是 user:123:cart?MongoDB 至少能从文档结构里看出购物车是嵌在用户文档里的数组,但 Redis 的 key 是扁平的、不透明的字符串,没有任何结构可以推理。
我记得第一次拿 LLM 做这个实验,场面相当滑稽:用户说「查购物车」,它直接 GET cart,拿到 nil 后陷入迷茫;然后开始暴力猜测 cart_123、carts:123、shopping-cart-123……命中率惨不忍睹。
后来我想通了:这不是模型笨,是信息缺失。Redis 不提供任何「有哪些 key 长得像购物车」的元信息。Agent 面对 Redis 的处境,是一个人在黑黢黢的洞穴里找洞口——每个洞口都长一样,但它不知道哪个通向宝藏。
所以真正有效的解法不是让 LLM 更聪明,而是给它探索装备:允许它 SCAN 匹配模式、用 TYPE 探测值类型、查看过期时间,甚至维护一份 key 前缀说明文档当上下文。我后来给项目加了一个 redis_schema_hint 工具,返回所有 key 前缀及用途说明,Agent 的准确率从不到三成涨到九成以上。
这件事让我明白,Agent 操作 Redis 的交互模式是「探索-发现」。它不是在查询结构化的库,而是拿着地图碎片在寻宝。Redis Stack 的 JSON 和JSON类型支持缓解了一部分问题,但你依然绕不开 key 的寻址。
Elasticsearch:拼接筛选器,但要理解分析器
Elasticsearch 站在第三个阵营。底层是倒排索引,查询方式是一组筛选器的组合。
想一下倒排索引是什么:MongoDB 是超市,你在货架间找商品,每个商品包装上信息齐全;Elasticsearch 是图书馆的目录柜,你不逛书架,先从目录卡里找到「哪些书提过 Python」,再拿着编号精准取书。
这种结构决定了 Elasticsearch 的查询不描述文档形状,而是筛选索引条目。match、term、range、bool——所有查询都落在几个基本筛选器上,Agent 的工作看起来像拼接条件,比 MongoDB 轻松得多。
但这里藏着一个大坑:match 和 term 的分词语义不一样。match 会先对你的输入做分词,再跟索引里的词元比较;term 不做分词,拿整个输入去精确匹配词元。用户问「查标题包含 Apple 的新闻」,Agent 老老实实写了 term 查询,什么都搜不到——因为 mapping 里 title 字段可能用了 standard 分析器,存的词元是小写的 apple。
这个坑我踩过很多次,以至于后来看到 Agent 输出 term 查询,都会下意识问一句:你确定这个字段是 keyword 类型吗?
Elasticsearch 的交互模式可以总结为:探查 mapping,理解分析语义,再拼接筛选器。它跟 MongoDB 的「描述形状」有本质不同——Agent 不是在描述数据长什么样,而是在描述哪些 token 应该被命中。相关细节可以翻Elasticsearch match 查询文档。
一张表看清三种交互模式的本质差异
| 维度 | MongoDB | Redis | Elasticsearch |
|---|---|---|---|
| 数据模型 | 文档(嵌套JSON) | 键值/数据结构 | 倒排索引文档 |
| 查询本质 | 描述目标文档形状 | 精确命令+key寻址 | 拼接筛选器命中词元 |
| Agent核心能力 | 结构感知、分步设计聚合管道 | 探索 key、理解命名约定 | 理解分析器与 mapping 语义 |
| 主要翻车场景 | 聚合管道阶段顺序错乱 | 自创 key 名、误读 key 格式 | match/term 混淆、忽略 keyword |
| 交互模式 | 生成式:描述结构 | 探索式:寻找 key | 组合式:拼接筛选条件 |
同一个 LLM,三种心智模型
写到这里,分享一个让我彻底想通的瞬间。
有段时间我特别纠结:为什么不能让 Agent 统一方式操作所有数据库?SQL 统一了关系数据库,NoSQL 也该有一个通用接口吧。
后来我意识到:统一接口统一的是语法,统一不了语义。你给 MongoDB 套一个 SQL 层,还是要解释字段是数字还是数组;你给 Elasticsearch 套一个 SQL 层,还是绕不开 text 和 keyword 的区别以及分析器的存在。语法可以翻译,但「数据长什么样」「查询到底在做什么」,每个数据库都有自己的答案。
所以 Agent 操作三种 NoSQL 数据库,真正要学的是三种心智模型:
- MongoDB:数据是嵌套文档,查询是对形状的描述。
- Redis:数据是扁平 key,操作是命名空间里的探险。
- Elasticsearch:数据是分词后的倒排索引,查询是筛选器的组合。
同一个 LLM,换一种数据库,就要换一种思维方式。这是 Agent 工具调用最有趣的挑战——你以为在教模型用工具,其实在教它用不同方式思考数据。
几个常见的误解
为什么不直接用 MCP(Model Context Protocol)统一协议?
MCP 解决的是「如何把工具暴露给 Agent」的传输问题,没有解决「Agent 如何理解这个数据库的数据模型」的认知问题。即使所有库都通过 MCP 暴露接口,Agent 依然需要知道:这个 key 是它自己临时编的,还是 Redis 里真实存在的?这个字段是 keyword 还是 text?
有没有可能让数据库反过来适应 Agent?
有,而且正在发生。MongoDB Atlas 在推 Atlas Search 的自然语言查询,Redis Stack 加了查询引擎和 JSON 类型,Elasticsearch 把 ES|QL 当作一等公民。但「自然语言查数据库」在复杂聚合面前仍然脆弱,真正的方向是混合:Agent 规划,数据库执行,人在关键节点确认。
Agent 与数据库的关系,正在被重写
回到开头的问题:让 Agent 同时操作 MongoDB、Redis、Elasticsearch,最难的是什么?答案是心智模型的切换。
Agent 不会替代 DBA。它真正改变的,是 DBA 必须把隐式知识显式化:key 命名规范、mapping 设计、聚合管道的性能陷阱——这些以前存在 DBA 脑子里的东西,现在得变成工具描述、schema 文档和提示词上下文,喂给 Agent 当世界知识。
未来真正让我兴奋的是这一点:当数据库为 Agent 重新设计交互接口,Agent 要学的也许不再是 MongoDB、Redis 还是 Elasticsearch,而是「查询一个数据源」这个更底层的能力。到那个时候,这篇文章的对比,可能就变成了历史。
原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/708.html