让 AI 应用直接查数据库这件事,我最近在好几个项目里都反复折腾过。Dify + MySQL 这个组合,说难不难,但真上手绕弯的地方不少:插件怎么选、连接参数怎么填、表结构要不要喂给模型、账号权限给到什么程度,每一个细节都能决定最终效果是“能用”还是“经常翻车”。
Dify 是目前很常用的 LLM 应用开发平台,核心能力之一就是让大模型通过“工具”去调用外部系统。把 MySQL 接进来之后,业务方直接在对话框里问一句“上个月华东区销售额前三的客户是哪些”,模型自动完成 SQL 生成、查询、结果整理三步,这体验和以前提工单等报表完全不是一个级别。
这篇文章只聊一件事:怎么让 Dify 安全、稳定地访问 MySQL。我会按照自己实际走的接入路径,把准备、配置、提示词设计、参数调优和排查方法完整过一遍。适合正在用 Dify 搭 Agent、做数据分析对话,或者想给内部机器人加数据库查询能力的朋友参考。
1. 在Dify里接MySQL之前,先想清楚这几点
1.1 为什么最终绕不开数据库访问
现在的 AI 对话应用,很多场景都要求模型手里的信息是“最新”的。
比如客服机器人要查订单状态、售后进度;数据分析助手要回答“本周退款率是多少”“哪些 SKU 库存低于安全值”;内部知识问答要关联 CRM 里的客户等级、合同金额。这些数据都存在业务系统的 MySQL 里。
传统做法是预先把数据导出到向量库,或者让开发人员为每个问题写死一个查询接口。前者只能做到定期同步,数据新鲜度跟不上;后者的问题更直接——业务问题那么多,你根本写不完所有接口,而且每改一个查询维度都要重新开发联调。
让 Agent 直接连数据库、用自然语言生成 SQL,是这个矛盾下最务实的解法。它把“写查询接口”这件事变成了“描述查询意图”,数据库仍然是你唯一的真相源,不需要把数据复制一份出去。
1.2 Dify 在其中的角色是什么
我不太建议自己去写一套“LLM 调用数据库”的完整链路,因为中间涉及对话管理、工具调用、上下文维护、密钥管理,自己造轮子周期很长,而且很容易在“模型乱调用工具”“SQL 写错”“上下文被结果集撑爆”这些细节上反复返工。
Dify 把这几块都变成了平台能力:
- Agent 编排:由 LLM 决定何时调用数据库工具,你不用自己维护函数调用的循环逻辑;
- 工具管理:数据库插件装上就能用,密钥由平台统一保存,不会出现在提示词里;
- 会话隔离与日志:每个用户的对话独立存储,工具调用过程可回溯,出问题方便排查;
- 工作流补充:需要固定执行某个查询时,可以用工作流把流程固化下来,不依赖模型“自由发挥”。
也就是说,你只要把 MySQL 的账号密码填进去,把“工具该怎么用”写清楚,剩下的对话流程与模型调度交给 Dify 就行。这也是很多团队选它来搭内部 AI 应用的主要原因——接入成本集中在“数据准备”而不是“工程搭建”。
1.3 三条接入路径怎么选
我在 Dify 里实际尝试过三种接 MySQL 的方式,各有利弊,这里直接上对比:
| 接入方式 | 实现成本 | 维护成本 | 合适场景 |
|---|---|---|---|
| 插件市场安装 MySQL 工具 | 最低,界面填入连接参数 | 低,插件跟随平台升级 | 个人项目、快速验证、内部小工具 |
| 自定义 OpenAPI 工具 | 中等,需自建一个 SQL 查询接口 | 高,接口也要跟着维护 | 有统一数据服务层、需要对 SQL 做二次包装的团队 |
| 工作流代码节点直连 | 中等,写 Python 代码 | 中,依赖沙箱环境 | 查询逻辑固定、参数变化、不需要模型自由发挥的场景 |
我自己的推荐很明确:只要能装插件,就用插件工具方式。理由有三点,一是配置量最小,不需要额外写服务;二是模型调用工具时,自然语言到 SQL 的流程已经封装好了;三是出问题时错误信息相对直观,不用跨系统排查。
自定义 OpenAPI 的方式适合企业里已经有统一数据网关的团队——你可以把权限控制、SQL 审计、限流都做在那一层。工作流代码节点则适合“查什么基本固定,只是参数变化”的场景,比如每天定时汇总报表、按传入的日期查某张表的统计值。
2. 动手前:MySQL、Dify 和表结构都要梳理
2.1 创建最小权限的 MySQL 账号
我见过不少人在配置 Dify 数据库工具时直接填 root 账号,这个习惯非常危险。Agent 的调用入口是模型,模型生成 SQL 有一定随机性,万一某次生成的 SQL 带上了 DELETE 或者 DROP,root 权限下数据被清空是完全可能的事。
所以第一步,先在 MySQL 里创建一个专用账号,只给只读权限:
CREATE USER 'dify_agent'@'%' IDENTIFIED BY 'StrongPass123!'; GRANT SELECT, SHOW VIEW ON yourdb.* TO 'dify_agent'@'%'; FLUSH PRIVILEGES;这里我一般还会顺手把 SHOW VIEW 也加上,因为有些模型生成的查询会用到视图,没有这个权限会直接报错。如果你的业务表分布在多个库,就按需授权,不要让 Agent 拿到全实例的访问能力。
权限上限定了“只读”,再加上提示词层面的约束,相当于上了两道锁。即使模型抽风生成了一条 UPDATE,数据库也会直接拒绝执行。
2.2 确认 Dify 到 MySQL 的网络路径
这个环节踩坑的人最多。很多人配置完连接发现连不上,第一反应是账号密码写错了,其实往往是“localhost”这个地址在捣鬼。
如果你是用 Docker 部署 Dify,那 Dify 容器里的 localhost 指向的是容器自己,不是宿主机上的 MySQL。反过来,如果 MySQL 也跑了 Docker 容器,那就要看两个容器是不是在同一个 Docker 网络里。
我的做法分两种情况:
- Dify 和 MySQL 都在 Docker Compose 编排里:直接在连接配置里填 MySQL 的服务名,比如 mysql,Compose 自带 DNS 解析,最省事;
- MySQL 是独立机器或本机服务:填宿主机在 Docker 网络里的 IP,或者直接填局域网 IP。注意,宿主机 IP 不是 127.0.0.1,可以用
ip addr查一下; - 某些场景下也可以用宿主机的网关地址,但不如直接填具体 IP 直观。
配完之后一定要先做连通性验证,最简单的就是telnet <host> 3306或者nc -zv <host> 3306,通了再往下走,不通就先解决网络问题,别急着怀疑工具配置。
2.3 把表结构整理成模型看得懂的样子
MySQL 工具本身只负责“执行 SQL 并返回结果”,模型并不知道你的库长什么样。如果不在提示词里把表结构说清楚,模型就只能靠猜,生成出来的 SQL 大概率是错的。
这一步没有捷径,只能老老实实把表结构清理一遍。我做这件事的顺序是:
- 拉出所有相关的表名和注释:
SHOW TABLE STATUS; - 逐表查看字段注释:
SHOW FULL COLUMNS FROM orders; - 整理出业务理解需要的中文说明,写进系统提示词。
我强烈建议把字段的中文含义写清楚,别只丢原始英文名。比如字段amt,你告诉模型这是“订单金额(元)”,模型生成聚合条件的时候会准确得多。后面我会专门说提示词怎么组织,这里先记住一个结论:表结构描述得越接近业务语言,SQL 准确率越高。
3. 核心实操:把 MySQL 工具接入 Dify
3.1 安装 MySQL 插件并配置连接参数
新版本的 Dify 都支持插件市场。进入平台后,在插件管理里搜索 MySQL 或 Database 相关的官方插件,安装即可。装好之后,在 Agent 应用的工具列表里就能看到刚安装的数据库工具。
配置连接参数时,我一般这样填:
| 配置项 | 填什么 | 注意事项 |
|---|---|---|
| 主机地址 | 见 2.2 节讨论的地址 | 容器间不要填 localhost |
| 端口 | 3306 | 确认 MySQL 实际监听端口 |
| 用户名 | dify_agent | 最小权限账号 |
| 密码 | 上一步设置的密码 | 平台内部加密保存 |
| 数据库名 | yourdb | 默认连接的库 |
填完之后先点测试连接,成功后再进应用里添加工具。如果测试失败,先把错误信息记下来,后面第五部分专门讲怎么排查。
这里多说一句,有些插件版本可能还支持额外参数,比如连接超时时间、字符集,如果你看到 charset 或者 encoding 之类的选项,直接填 utf8mb4,省得后面中文乱码再折腾。
3.2 编写系统提示词与工具描述
工具配置好只是第一步,真正决定 Agent 能不能正确查数据的,是提示词怎么写。模型是依靠提示词里的表结构描述来生成 SQL 的,这部分写得多细致,SQL 就有多准确。
下面是我在项目里用下来效果不错的一段系统提示词模板:
你是企业的数据分析助手,你的工作是回答用户关于业务数据的任何问题。 你可以使用数据库查询工具。数据库中有以下核心表: 1. orders 订单表 - order_no VARCHAR:订单编号 - customer_id BIGINT:客户ID,关联 customers.id - amount DECIMAL(10,2):订单金额 - status VARCHAR:状态,取值范围 pending / shipped / completed / refunded - created_at DATETIME:下单时间 2. customers 客户表 - id BIGINT:客户ID - name VARCHAR:客户姓名 - phone VARCHAR:联系电话 - segment VARCHAR:客户分层,取值范围 gold / silver / normal 查询时必须遵守: - 只允许执行 SELECT 查询,禁止 INSERT / UPDATE / DELETE / DROP / ALTER; - 所有查询默认附带 LIMIT 100; - 如果用户问题与业务数据无关,不要使用工具,直接说明无法回答; - 统计类问题优先使用 COUNT / SUM / AVG,并按业务维度合理分组。这段提示词有几个关键点:
- 每个字段都给了中文注释,还有取值范围。取值范围尤其重要,模型知道了 status 的可枚举值,就不会在 WHERE 条件里写出根本不存在的状态;
- 明确了“只读”边界,作为安全性的第二道防线;
- 默认 LIMIT 100,防止一次查询把几百 MB 结果集拉到对话上下文里,直接把 Token 额度烧光;
- 最后一条引导模型在统计问题上正确使用聚合函数,减少“把明细拉出来人肉数数”的低效行为。
除了系统提示词,工具描述也要写。工具描述是模型决定“要不要调用这个工具”的判断依据。我的写法是:
查询 MySQL 数据库业务数据。当用户询问订单、客户、销售统计、库存、售后等业务数据时,使用该工具。支持自然语言转 SQL 查询,只读。描述里包含了触发场景关键词和工具能力边界,既能让模型在需要时主动调用,也减少了无关问题被错误转发到数据库的概率。
3.3 首次测试:自然语言转 SQL
配置完成、提示词就位,接下来就是最让人兴奋也最容易失望的环节——实测。我建议先拿三条由易到难的问题做验收,不要上来就丢复杂业务问题。
第一类,单表简单查询:
上周一共有多少笔订单?
这种问题模型基本不会出错,预期生成:
SELECT COUNT(*) FROM orders WHERE created_at >= '上周起始' AND created_at < '本周起始';第二类,带过滤条件的查询:
帮我查一下 status 是 completed 的订单里,金额最大的 5 笔。
预期会生成带 WHERE 和 ORDER BY LIMIT 的 SQL。这一步开始考察模型对枚举值的理解是否正确。
第三类,多表关联和聚合:
按客户分层统计一下每个层级的订单总金额。
预期生成关联查询和 GROUP BY。这一步如果表结构描述不到位,往往就开始出问题了,比如关联字段猜错、GROUP BY 和 SELECT 列不匹配。
每跑完一条,我都会去 Dify 的日志里看一眼模型生成的原始 SQL。这一步很有价值,能直观看到模型对表结构的理解偏差在哪里,是字段含义没看懂,还是取值范围猜错,还是关联关系不明确,然后针对性修改提示词。
4. 正式用上之后,注意这几个关键细节
4.1 表注释和命名规范是 SQL 准确率的幕后推手
接 MySQL 不是配置完就能高枕无忧的,模型生成 SQL 的准确率,很大程度上在数据库设计阶段就已经决定了。我在一个模拟项目里做过对比:同一套业务问题,在表结构注释不全的情况下,SQL 出错率大约有三到四成;把表注释、字段注释和取值范围补齐之后,出错率骤降到一成以内。
所以如果你有权限修改表结构,强烈建议做好两件事:
- 给表和字段都加 COMMENT,比如
amount DECIMAL(10,2) COMMENT '订单金额(元)'; - 字段命名尽量语义化。
customer_id比c_id好懂,created_at比create_time不易混淆。太简短的缩写会让模型多一层猜测。
没有权限改表结构也没关系,把中文注释写进系统提示词,效果基本等同。我之前接一个老系统时,字段名全是拼音缩写,后来完全靠提示词里的“字段对照翻译”把准确率拉回来的,这招同样管用。
4.2 安全边界:权限之外还要做三层防护
只读账号是第一道防线,但我不建议只依赖它。Agent 场景的安全要分层设计,我自己的实践是三层:
第一层,数据库权限。就是前面创建的 dify_agent 账号,从根上禁掉非查询语句。
第二层,提示词约束。系统提示词里明确禁止非 SELECT 操作,并给模型设定“查不到就说明”的行为边界,让它不要为了凑结果乱编。模型会遵守提示词的程度有时不太好预期,所以这一层属于软约束,不能当唯一保障。
第三层,查询行为控制。在工具描述和提示词里强制 LIMIT,防止单次查询拉取过大结果集。如果 MySQL 工具支持配置查询超时,把它设成 5 到 10 秒,避免复杂查询把对话响应拖死。
这里还有一个细节:不要让 Agent 连接生产库的写库账号。很多团队内部项目图省事,直接复用业务系统账号,一旦出了安全问题,线上数据被波及,代价远大于“多建一个只读用户”的几分钟工作量。
4.3 性能与超时:别让一次查询拖垮整个对话
Agent 对话是交互式的,用户等不了几十秒才收到结果。数据库查询如果太慢,整个对话体验会非常糟糕。我踩过几次这类坑之后,总结了以下几个控制手段:
- 给高频查询的字段加索引。比如订单表的 created_at、customer_id、status,客户表的 segment,这些都是 WHERE、JOIN、GROUP BY 的常客。索引这事只要在配置提示词前顺手做掉就行。
- 在提示词里约束默认 LIMIT,并引导模型对统计问题优先用聚合函数,而不是拉明细再数。
- 如果插件或节点支持 SQL 超时设置,直接开启。MySQL 里也可以用
SET STATEMENT max_execution_time=5000 FOR SELECT ...这类语法做单条限制,不过这个要看工具实现,不保证每个插件都支持。 - 对于数据量很大的表,考虑给模型提供一张“预聚合好的统计视图”,让模型查视图而不是查明细大表。查询速度提升非常明显。
这里还要提一个容易忽略的成本问题:返回结果集越大,转成 JSON 之后消耗的 Token 越多。同一个查询,如果本来 10 行就能说明问题,却返回了 500 行明细,既浪费预算又稀释了模型对重点信息的注意力。所以 LIMIT 不只是性能问题,也是上下文质量问题。
5. 常见问题与排查实录
5.1 数据库连接失败怎么查
连接失败是最常见的开局问题。特征是在 Dify 里测试连接时直接报错,或者在对话中模型调用工具时报连接异常。
我把现场常见的错误信息和排查思路整理成了表格:
| 错误特征 | 常见原因 | 排查动作 |
|---|---|---|
| Unknown MySQL server host | 主机地址填错 | 检查填的是 IP 还是服务名,容器内能否解析 |
| Can't connect to MySQL server on ... | 网络不通或端口未开放 | telnet 测试 3306,检查防火墙、安全组 |
| Access denied for user ... | 账号密码错误或授权不足 | 检查用户是否存在、密码是否正确、授权来源 |
| SELECT command denied to user ... | 账号没有对应库表的 SELECT 权限 | 重新执行 GRANT SELECT,注意别写错库名 |
这类问题十有八九出在“网络路径”和“账号授权”两步,按表格从上往下排查,基本都能定位。每次排查的时候我都建议先在命令行里用同一个账号手动连一次 MySQL,这一步能快速区分是 MySQL 侧的问题还是 Dify 侧的问题。
5.2 生成的 SQL 不符合预期
SQL 本身能跑,但不是用户想要的结果,这种情况比连不上更磨人,因为它不会直接报错,要对着日志一条一条看。
我自己遇到最多的是三类:
- 表名或字段名猜错。比如实际是 customer_level,模型生成的是 customer_grade。解决办法是在提示词里把字段全名列得更清楚,最好连字段类型和含义一起给。
- WHERE 条件里的枚举值错误。模型不知道 status 有哪些值,就可能写出 status = 'succ' 这种不存在的值。解决办法是我前面说的,把取值范围直接列出来。
- 日期过滤的边界问题。模型经常对“本周”“上月”这类相对时间的起点理解不准。我后来在提示词里加了指导:涉及时间范围时以当前日期为准,向前推对应周期。必要的话,可以进一步细化日期换算规则。
排查这类问题时,Dify 的调试日志一定要利用起来。日志里能看到模型生成的原生 SQL,把这条 SQL 拿到 MySQL 里手动执行,对比预期结果,就能判断是 SQL 生成的问题还是查询执行的问题。
5.3 中文乱码与数据时区问题
乱码这个问题,一般出现在工具返回结果包含中文的时候。常见表现是对话输出里的中文变成了一串问号或乱码字符。
根因大多是连接字符集不一致。MySQL 连接默认字符集可能不是 utf8mb4,而 Dify 那边按 UTF-8 解析,两边就对不上了。解决办法:
- 在连接参数里显式指定字符集;如果工具配置里有 charset 或 encoding 选项,填 utf8mb4;
- 如果工具不支持配置字符集,可以试试在提示词里提示“数据库连接使用 utf8mb4 编码”,有些实现会把这个信息带上;
- 如果是通过自定义 API 接入,代码里连接 MySQL 时加上
charset='utf8mb4'参数。
时区问题则是另一个隐蔽坑。MySQL 的 datetime 字段如果存的是 UTC 时间,模型查询“今天”的数据时,按北京时间理解就会差 8 个小时。我一般的做法是:连接参数里设置时区,或者在提示词中明确“数据库时间统一为北京时间”,让模型在生成时间条件时做对应换算。
5.4 工具执行时报语法错误
还有一种常见情况:SQL 生成出来有语法错误,工具直接返回执行失败。这类问题通常在日志里能看到明确的 SQL 语句和 MySQL 的报错信息。
排查时我第一件事是把报错的 SQL 复制出来,在 MySQL 客户端里手动跑一遍。经常发现的问题包括:
- 表名或字段名没有加反引号,而表名恰好是保留字,比如 order、group 这类词;这种情况让模型在生成 SQL 时给表名和字段名加反引号,可以明显降低报错率;
- 多个条件之间少了空格或括号不匹配,模型在生成长 SQL 时偶尔会犯这个毛病;
- GROUP BY 的字段没有全部出现在 SELECT 中,被 MySQL 的非严格模式限制挡住了。
解决这类问题最有效的方法是“在提示词里给 SQL 写规范要求”。比如直接告诉模型:生成 SQL 时注意语法规范,字符串值用单引号,表名和字段名用反引号包裹,函数和关键字用大写。这比反复测试要好很多,一次提示到位,后面就很少再出语法低级错误。
最后再分享一个我个人的经验。在这个项目里投入产出比最高的动作,不是反复调模型参数,而是把表结构整理好、把提示词写清楚。刚开始我也以为问题出在模型不够聪明,后来发现大部分错误都是因为模型没有拿到足够的“业务上下文”。当我花半小时把字段注释、枚举值、关联关系写进提示词之后,之前反复出现的那批 SQL 问题直接消失了。
如果你正准备在 Dify 里接 MySQL,别急着打开界面配置连接,先回数据库侧把“最小权限账号 + 表结构整理”这两件事做掉,后面会顺畅非常多。第一次跑通自然语言查询之后,记得顺手把日志里模型生成的 SQL 翻一遍,那是改进提示词最直接的素材。