DeepSeek+Dify+达梦:自然语言查询数据库的NL2SQL落地实践
2026/9/16 4:52:36 网站建设 项目流程

1. 项目背景与整体方案设计

做一个"输入一句话,自动查出数据库里的数据"的功能,这个问题我琢磨了很久。起因是公司内部经常有业务部门提数据需求,今天要个订单统计,明天要个库存明细,每次都得数据团队写SQL、跑查询、导表格再发过去,一通操作下来大半天就没了。正好这两年DeepSeek这类大模型在自然语言理解和代码生成上表现相当能打,Dify这类LLM应用开发平台又把模型接入、Agent编排和对外服务封装的门槛拉低了不少,再加上项目本身跑在国产化环境,业务库用的是达梦——于是就有了这套"DeepSeek + Dify + 达梦"的组合方案。这篇文章把整个实现过程、踩过的坑和优化心得完整整理出来,给正在做数据查询服务、报表平台,或者被"查数据还得写SQL"困扰的读者一个可以直接参考复现的落地路径。

1.1 为什么需要自然语言查询数据库

先说一个实际场景。业务部门真正想要的不是一张报表,而是"一个问题的答案"。比如"上个月华东区退货率最高的商品有哪些""最近30天新注册用户的下单转化率是多少",这类问题放在数据团队手上,写SQL几分钟,但排队等排期可能等到下周。如果能让业务人员直接用大白话提问,系统自动生成SQL、执行查询、再把结果用自然语言回传,整个过程从"提需求-排期-取数-加工-反馈"压缩成"提问-回答",效率提升是数量级的。

自然语言查询数据库,行业内一般叫NL2SQL(Natural Language to SQL)。这个方向其实很早就有人在研究,早期用规则模板、序列到序列模型,效果一直差强人意。真正让它变可用,靠的是大语言模型的崛起——模型本身已经学会了SQL语法、常见表结构设计套路和大量业务查询表达,只需要给它明确的表结构信息和约束规则,它就能生成质量相当不错的SQL。DeepSeek在这个场景里优势很明显:中文理解能力强,代码生成稳定,API价格相比同类模型友好很多,而且支持私有化部署,对数据敏感的企业项目来说非常关键。

1.2 技术选型:为什么是DeepSeek + Dify + 达梦

选型的时候其实纠结过好几轮。最初考虑过直接用Python写一个脚本,调DeepSeek的API,拿到SQL后用JDBC执行达梦,再让模型把结果翻译成自然语言。这个方案最轻量,适合自己玩,但真要给业务部门用,还缺对话管理、权限控制、日志审计、API封装这些东西,全自己写工作量不小。后来看到了Dify,它是开源的LLM应用开发平台,把模型管理、Prompt编排、知识库、工作流、Agent工具这些能力都做成了可视化配置,社区版免费,部署也简单,于是果断转向"Dify + 自定义工具"的架构。

三个组件的职责划分是这样的:

  • DeepSeek:负责理解用户自然语言,生成SQL,并把查询结果整理成读得懂的答案。它是整个系统的"大脑"。
  • Dify:负责应用编排和运行管理,包括模型接入、会话上下文、智能体工具调用、日志追踪和API网关。它是"躯干"。
  • 达梦数据库:负责存储业务数据,执行最终SQL,返回结果集。它是"底座"。

这套组合还有一个额外的好处:开发出来的应用不绑定具体数据库。如果哪天要把达梦换成其他主流关系型数据库,只需要改查询服务里的连接配置和方言适配,上层Prompt和Dify编排完全不用动。

1.3 整体架构与数据流

整个系统的数据流是这样的:用户在Dify的对话界面输入自然语言问题,DeepSeek根据预设的表结构信息生成SQL,Dify通过自定义工具把SQL发送给一个轻量的查询服务,查询服务用JDBC连接达梦执行SQL并返回JSON结果,最后DeepSeek把结果集转化成自然语言回答,展示给用户。

这里有个关键设计:Dify本身并没有直接连达梦的官方插件,所以我们需要在中间加一层"查询服务"。这个服务可以做得非常简单,就是一个HTTP接口,接收SQL,返回执行结果。我第一版用FastAPI写的,一共不到100行代码,部署在应用服务器上,只有内网可以访问,安全风险可控。Dify侧通过"自定义工具"的方式接入这个HTTP接口,整个过程不复杂,后面我会把核心代码和配置完整贴出来。

2. 环境准备与基础部署

2.1 DeepSeek模型的两种接入方式

接入DeepSeek有两条路:调用官方API,或者本地私有化部署。这个选择直接影响后面的网络架构和安全设计。

方式一:官方API。适合快速验证和中小规模使用。去DeepSeek开放平台注册账号,创建API Key,在Dify的模型供应商里选择DeepSeek并填入Key就能用,整个过程不到十分钟。优势是零运维,模型能力由官方持续更新;劣势是数据要经过外部API,对于数据安全要求极高的企业内网环境,这一条可能直接不通过。

方式二:本地部署。适合对数据安全有硬性要求,或者需要完全离线运行的项目。DeepSeek开源了多个尺寸的模型权重,常见的有7B、16B、32B级别。7B模型大概需要16GB显存,32B模型需要64GB以上显存,部署时可以用vLLM或者llama.cpp做推理加速。成本方面,一台双卡4090或者单卡A800的服务器基本就能跑起来,对中小企业来说是可以接受的投入。本地部署的好处是数据完全不出内网,响应延迟也更可控。

我在实际项目里采用的是官方API验证方案,先跑通整个流程,后续如果客户要求数据不出域,再平滑切换到本地部署。Dify里切换模型供应商非常简单,不需要动应用逻辑,这也是选Dify的加分项。

2.2 Dify平台的部署要点

Dify社区版推荐用Docker Compose方式部署,官方提供了一键部署脚本,对机器要求不高,4核8G内存的服务器就能流畅跑起来。部署流程如下:

# 拉取项目代码,建议锁定版本 git clone https://github.com/langgenius/dify.git cd dify git checkout 1.17.1 # 复制环境变量示例 cp .env.example .env # 启动服务 docker compose up -d

启动完成后,浏览器访问 http://服务器IP:80 就能进入Dify控制台。Dify本身依赖PostgreSQL、Redis和一些中间件,Compose编排会自动拉起,不需要手动安装。

这里有一个非常容易踩的坑:Dify拉取镜像失败。因为Docker Hub在国内网络环境下经常超时,第一次部署时好几个镜像拉不下来,卡了快一个小时。解决办法是在 /etc/docker/daemon.json 里配置镜像加速器,然后再重启Docker服务,亲测有效。配置完成后重新 docker compose pull 就能正常拉取。

{ "registry-mirrors": ["https://docker.m.daocloud.io"] }

Dify的版本更新节奏比较快,社区版1.10以后加入了多租户能力,1.17.x系列在工作流和知识库流水线方面又有不少增强。生产环境建议锁版本部署,不要每次更新都盲目升级,避免功能变更影响业务。部署完成后,记得第一时间在"设置-模型供应商"里接入DeepSeek或其他模型。

2.3 达梦数据库准备与连接配置

达梦数据库的安装不展开讲,网上的教程很多。这里只说和本项目强相关的几步:建业务库、建只读账号、确认JDBC驱动和连接参数。

达梦的JDBC驱动包一般叫 DmJdbcDriver18.jar,在数据库安装目录的 drivers/jdbc 下能找到。连接URL的格式是:

jdbc:dm://192.168.1.100:5236?schema=DMDB

驱动类名是 dm.jdbc.driver.DmDriver,默认端口5236。需要注意,达梦对大小写敏感,未加引号的表名和字段名会被自动转为大写,这一点和Oracle很相似。这意味着大模型生成的SQL如果用了小写的表名和字段名,直接执行会报"无效的表名或视图名",这是我们踩过的最大的一个坑,后面会专门讲怎么解决。

在给查询服务配置连接池时,我用的是HikariCP,配置里有一个关键项:连接测试语句。MySQL可以用 SELECT 1,达梦兼容Oracle方言,写成 SELECT 1 FROM DUAL 更稳妥。还有一个容易忽略的参数是驱动类名,HikariCP要求必须显式指定 driverClassName,否则会报"Failed to load driver class"。

spring: datasource: driver-class-name: dm.jdbc.driver.DmDriver jdbc-url: jdbc:dm://192.168.1.100:5236?schema=DMDB username: query_user password: xxxxxx hikari: connection-test-query: SELECT 1 FROM DUAL maximum-pool-size: 5 minimum-idle: 1

权限方面,一定要单独创建查询用账号,只授予SELECT权限,千万不要用DBA账号。这一点在涉及大模型自动生成SQL的场景里尤其重要——模型再聪明也可能生成 DELETE 或 UPDATE,只有从权限层面锁死,才能保证数据库安全。

3. 核心实现:在Dify中搭建自然语言查询应用

3.1 应用类型选择与创建

Dify里有多种应用类型,本项目选择的是Agent应用。为什么不用普通聊天助手或工作流?因为自然语言查询数据库天然需要"工具调用"能力:用户输入问题后,模型需要判断是否要查库、生成SQL、调用查询工具、根据结果回答,这是一个典型的多步骤Agent行为。Agent类型允许模型自主决定是否调用以及何时调用工具,对话体验最自然。

创建一个Agent应用的步骤很简单:进入Dify工作台,点击"创建空白应用",选择"Agent"类型,命名后进入编排界面。在编排界面左侧选择模型为DeepSeek,中间区域编写系统提示词,右侧配置工具,下面详细拆解。

3.2 核心Prompts设计与NL2SQL逻辑

Agent效果好不好,一半看模型,一半看Prompt。我的System Prompt经过多轮迭代,核心内容如下:

你是一名资深的数据分析师,负责将用户的自然语言问题转换为SQL查询语句。 ## 数据库信息 数据库类型:达梦数据库(兼容Oracle语法) 当前日期:{{current_date}} ## 表结构信息 表 orders(订单表): - order_id BIGINT 订单ID,主键 - customer_name VARCHAR(64) 客户姓名 - region VARCHAR(32) 区域(华东/华北/华南/西南等) - product_id BIGINT 商品ID,关联products表 - order_amount DECIMAL(10,2) 订单金额(元) - order_date DATE 下单日期 表 products(商品表): - product_id BIGINT 商品ID,主键 - product_name VARCHAR(128) 商品名称 - category VARCHAR(32) 商品分类 - unit_price DECIMAL(10,2) 单价(元) ## 工作流程 1. 理解用户的查询意图 2. 根据表结构生成合法的SQL查询语句 3. 调用query_database工具执行查询 4. 将查询结果整理成通俗易懂的自然语言回答 ## 硬性规则 1. 只能执行SELECT查询,禁止生成INSERT/UPDATE/DELETE等写操作SQL 2. 查询结果必须限制在100条以内,防止返回数据量过大 3. 如果用户的问题涉及的表不存在于表结构信息中,必须以"数据表中暂无相关信息"回答 4. 严禁捏造查询结果,工具返回什么就回答什么 5. 所有表名、字段名在SQL中一律使用大写(达梦数据库大小写规则) 6. 涉及日期计算时,以当前日期为准,可以使用SYSDATE函数辅助计算

这个Prompt里有两个细节值得细说。第一个是"表结构信息",这是模型生成正确SQL的基础,我把业务表整理成这样的字段清单,每个字段标注类型、含义和典型值,模型生成SQL的准确率明显提高。如果表特别多,可以只录入最常用的几张核心表,避免上下文过长。第二个是"当前日期",我用Dify的变量功能在会话开始时动态注入,这样模型在回答"上月""本周"这类时间表达时,能正确换算日期范围,不会算错。

3.3 连接达梦数据库:工具与服务配置

Dify没有达梦数据库的原生工具,所以需要先在Dify的"工具"页面创建一个自定义工具,指向我们自己写的查询服务。

查询服务我用FastAPI实现,核心代码大致如下:

from fastapi import FastAPI, Request import dmPython # 达梦的Python驱动,需要单独安装 import json app = FastAPI() @app.post("/query") async def query_database(req: Request): data = await req.json() sql = data.get("sql", "") # 安全检查:只允许SELECT if not sql.strip().upper().startswith("SELECT"): return {"error": "Only SELECT statements are allowed"} conn = dmPython.connect(user="query_user", password="xxxxxx", server="192.168.1.100", port=5236) try: cursor = conn.cursor() cursor.execute(sql) columns = [col[0] for col in cursor.description] rows = cursor.fetchmany(100) result = [dict(zip(columns, row)) for row in rows] return {"columns": columns, "rows": result, "count": len(result)} finally: conn.close()

这个服务有几个细节:强制检查SQL前缀为SELECT,防止SQL注入和误操作;fetchmany(100)限制返回条数;返回时带上列名,方便模型理解结果含义。生产环境建议用连接池替换每次请求新建连接的方式,性能会好很多。

在Dify自定义工具配置里,OpenAPI Schema填写Swagger生成的JSON,或者手动编写,关键是要声明 /query 接口的请求参数格式。配置完成后,Agent会收到一个名为"query_database"的工具,模型会在需要查库时自动调用。

3.4 从单轮到多轮:对话式查询优化

一个容易被忽略的需求是多轮对话。业务人员经常会连续提问:"上个月华东区订单量多少?""那退货率呢?""和上上个月比呢?"——如果没有对话上下文管理,第二句"那退货率呢?"模型根本无法理解。

在Agent应用里,Dify会自动携带会话历史,所以大模型能通过上下文推断出"退货率"指的是上一轮提到的订单数据。我只需要在Prompt里加一句"注意结合对话历史理解用户的追问",效果就出来了。

但这里有个隐患:多轮对话会让Token消耗快速增长,而且上下文过长可能导致模型注意力分散。我建议在Dify的会话设置里把历史消息窗口限制在10轮以内,既保留有效的上下文信息,又控制成本开销。实测下来,10轮对话以内的连带指代识别率挺高,超过之后就开始出现理解偏差,所以这个值是够用的。

4. 核心环节实现细节与效果验证

4.1 第一轮查询:从提问到SQL的完整流程

部署完成后的第一次测试,我用的问题是:"上个月华东区销售额最高的3种商品是什么?"

整个流程分几步走:用户输入问题,DeepSeek识别出这是一个需要查询数据库的问题,根据表结构信息生成SQL,调用query_database工具执行,拿到结果后整理成自然语言回答。

模型生成的SQL大致是这样:

SELECT p.product_name, SUM(o.order_amount) AS total_sales FROM orders o JOIN products p ON o.product_id = p.product_id WHERE o.region = '华东' AND o.order_date >= TRUNC(SYSDATE, 'MM') - INTERVAL '1' MONTH AND o.order_date < TRUNC(SYSDATE, 'MM') GROUP BY p.product_name ORDER BY total_sales DESC FETCH FIRST 3 ROWS ONLY

这里值得表扬一下DeepSeek的地方是它主动考虑了达梦的Oracle方言,用了TRUNC和INTERVAL来做月份计算,而不是写死日期,这样每月运行都能自动对齐上个月的时间范围。执行结果正常返回,Agent最终给出的回答是"上个月华东区销售额最高的商品依次是XXX、XXX、XXX,销售额分别为X元、X元、X元",并且附带了"查询范围是本月1号到30号"的说明,业务部门一看就懂。

4.2 复杂自然语言处理与SQL生成技巧

第一轮查询跑通之后,我开始测试更刁钻的问题。比如"按区域统计一下今年的客单价趋势"——这个"客单价"涉及计算逻辑,通常定义为总销售额除以订单数。模型对这个业务概念的理解,取决于表结构里有没有明确说明,Prompt里如果没有写,模型可能生成错误的计算方式。

我的办法是在表结构信息里补充业务口径说明。比如:

客单价 = 总销售额 / 订单数 退货订单 = 订单状态字段为'已退货'的订单

把业务口径直接喂给模型,生成SQL的准确率会大幅提升。另外,对于不常见的查询表达,我在Prompt底部加了一组Few-shot示例,比如:

示例1: 用户:北京地区的平均订单金额是多少? SQL:SELECT AVG(order_amount) FROM orders WHERE region = '北京' 示例2: 用户:每个商品的销量排名,最高的前5个 SQL:SELECT product_id, COUNT(*) AS sale_count FROM orders GROUP BY product_id ORDER BY sale_count DESC FETCH FIRST 5 ROWS ONLY

Few-shot示例控制在3组以内就够了,太多会挤占上下文长度,效果提升不明显。经过这轮优化,"客单价""同比环比""TopN"这类复杂需求基本都能正确转化为SQL。

4.3 大小写问题与达梦方言适配

前面提到的达梦大小写问题,这里展开说。第一次测试时,模型生成的SQL全是小写表名,执行直接报错。排查了半天才发现,达梦在默认配置下,不带引号的标识符会自动转为大写存储,而小写的表名根本找不到。

解决思路有两个层面。第一个层面是在Prompt里硬性约束,让模型生成SQL时表名和字段名全部使用大写。这个方法简单直接,但模型偶尔会忘记,不够保险。第二个层面是在查询服务里做一次大写转换,因为字段内容里可能包含大小写敏感的字符串值,不能无脑整体转大写,而是通过解析SQL中的关键字,只把表名和字段名转成大写,这个实现稍复杂一些。

我的最终方案是两个层面结合:Prompt里约束大写,查询服务里做了一层兜底处理——检测到SQL执行报"无效的表名"错误时,自动尝试把代码中涉及的表名字段名转大写重试一次,实际运行中兜底率不低,但确实增加了一些代码复杂度。如果你只是做内部验证,Prompt约束就够用了,生产环境还是建议把查询服务里的自动修正逻辑加上。

4.4 产品化封装与安全控制

验证阶段跑通之后,我给这个应用做了产品化封装。在Dify的"访问API"页面,可以创建API Key,调用Dify对外开放的接口,这样外部的报表系统、企业微信机器人、Web页面都可以接入。接口调用方式很简单,POST一个对话消息,同步返回结果,Dify已经把Agent内部的所有工具调用封装好了。

安全控制做了三层。第一层是数据库账号权限,只读账号 + 专用Schema,从根源上杜绝危险的写操作。第二层是查询服务层面的SQL前缀校验,同时限制了单次返回的最大行数和最大执行时间,防止业务人员问出一个全表扫描导致数据库性能雪崩。第三层是Dify层面的访问控制,API Key定期轮换,同时做了操作日志留痕,每次查询都能追溯到提问人、提问时间和生成的SQL。

5. 常见问题与排查技巧实录

5.1 Dify部署与镜像拉取问题

这个坑几乎每个部署Dify的人都会遇到。docker compose up -d 执行后,好几分钟没反应,最后报错全是"pull access denied"或"timeout"。排查思路:先确认不是镜像本身的问题,github.com/langgenius/dify 仓库的镜像都在Docker Hub上,国内直接访问不稳定。配置镜像加速器是最直接的解决方案,配置完执行 systemctl restart docker,然后重新拉取即可。如果加速器还是不行,可以尝试使用代理或者从镜像站手动下载再docker load导入,后者比较麻烦,加速器解决90%的问题。

5.2 达梦连接与驱动配置问题

查询服务启动时报各种连接异常,常见原因有三个。第一个是驱动类名写错,达梦有两个驱动类,老的叫 dm.jdbc.driver.DmDriver,新版本也有类似的,必须是全限定名,不能省略包名。第二个是连接URL格式不对,有的同学习惯套MySQL格式写成 jdbc:mysql://,那肯定报错,达梦的格式是 jdbc:dm://host:port。第三个是防火墙问题,5236端口没开放,从应用服务器telnet一下端口就能定位。

HikariCP连接池还有一个特殊配置项:connection-test-query。如果不配置,HikariCP默认用JDBC4的 isValid() 方法验证连接,达梦驱动对这个接口的支持不完善,可能导致连接池把正常的连接判定为失效。显式配置 SELECT 1 FROM DUAL 可以避免这个问题,这也是达梦社区里比较常见的处理方式。

5.3 NL2SQL准确率问题的调优思路

很多读者体验后反馈:"模型生成的SQL偶尔能用,偶尔不能用,怎么办"。准确率问题是NL2SQL系统的常态,我的优化思路按照优先级排序:第一,检查表结构信息是否完整准确,字段描述是否清晰,如果模型对字段含义模糊,生成SQL时只能靠猜,准确率必然低;第二,检查Prompt里的硬性规则是否覆盖了达梦方言和业务口径,把会出错的模式直接写成"禁止"或"必须";第三,增加Few-shot示例,把业务方最常问的10类问题写进示例,模型有样可依会稳定很多;第四,考虑在查询服务里增加SQL语法校验,生成后先解析再执行,语法错误直接抛给模型重试,而不是把错误抛给用户。

实测下来,前三个优化做完,常见问题的准确率已经从70%提升到90%以上。剩下的10%多属于长尾问题,需要持续收集真实提问,定期补充到示例库中,模型的稳定度会随着标注数据的积累稳步上涨。

6. 延伸思考与个人体会

这套方案做完之后,我自己最大的感触是:NL2SQL下SQL生成的"最后一公里"问题,难点真的不在于模型,而在于对业务的理解。同一个字段,在不同语境下含义可能完全不同;同一个指标,不同部门的统计口径可能还不一样。这些业务知识能不能有效传递到模型侧,决定了这个功能是好用还是鸡肋。

另外想提醒的是,自然语言查询数据库并不是要取代数据分析师。它擅长处理的是"已知问题、找答案"这类标准化查询,而真正需要分析师介入的,是那种"连问题都没想清楚"的分析场景。所以这套系统的定位应该是把数据分析师从重复的提数工作中解放出来,让他们去做更深层的分析和业务洞察。

我个人的建议是,如果你想在自己的项目里复现这套方案,不要一上来就搞一大堆表的全量接入,先挑一个业务域、两三张最核心的表,把Prompt和表结构信息打磨好,跑通一个完整的业务闭环。等业务方用起来、反馈真实问题时,再逐步扩充表范围和示例库。这样迭代节奏最稳,也能更早看到实际价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询