让大模型根据中文问题生成 SQL,十分钟就能做出演示;让它面对真实库表、模糊口径、敏感字段和高并发仍然可靠,却是另一类工程。本文以 Spring Boot、PostgreSQL 与大模型组合为背景,把系统拆成语义、生成、静态审查、受限执行和结果验证五道闸门,并说明权限、超时、行数上限、评测集与审计日志为什么比提示词模板更重要。

演示路径为什么会骗人

演示通常只有三步:把建表语句发给模型,模型返回 SELECT,应用执行并展示表格。在样例库中它很顺;到了生产,用户一句“上个月最好的客户”就暴露三个歧义:上个月按自然月还是最近三十天,“最好”按收入、利润还是复购,客户是否排除内部测试账号。

模型生成了语法正确的 SQL,只能证明它猜了一种解释。Text-to-SQL 的核心不是字符串生成,而是把自然语言、业务口径、数据库权限和可接受风险连接起来。

第一道闸门:先解析业务语义

不要立即把用户原句交给生成器。先得到结构化意图:指标、维度、过滤条件、时间范围、排序和结果数量。缺少关键字段时应追问,而不是让模型偷偷选择默认值。

例如“看华东上月销售前十”可以解析为:指标 paid_amount,维度 customer,区域 east_china,时间为上一个自然月,排序降序,限制 10 行。区域与指标都应来自受控词典,而不是允许模型任意拼接字段名。

业务语义层还应维护口径版本。若“销售额”从含税金额改为不含税金额,历史查询必须知道自己使用哪个版本,否则同一句问题在不同日期得到不可比较的结果。

第二道闸门:只给模型最小模式

把整个数据库的 DDL、注释和样例数据全部塞进提示词,既浪费上下文,也扩大敏感信息暴露。先根据业务域检索相关表和字段,再只提供必要模式、允许连接关系、字段含义和少量无敏感示例。

推荐建立语义目录,而不是直接依赖物理表名:

业务指标: paid_amount
定义: 已支付且未退款订单的含税实收金额
来源: analytics.order_fact.paid_amount
必需过滤: order_status = 'PAID' AND refunded = false
允许维度: day, region, customer_segment
敏感等级: internal

模型看到的是经过治理的查询能力。底层换表或字段重命名时,更新目录即可,不必期待模型从数百张表中重新猜关系。

目录检索本身也要纳入评测。若正确表没有进入候选上下文,后续模型再强也只能在错误模式里作答。因此需要分别记录“检索是否召回正确对象”和“生成是否使用正确对象”,避免把所有错误都归因于 SQL 生成模型。

第三道闸门:SQL 必须经过结构化审查

不要用正则表达式判断是否以 SELECT 开头。SQL 可以有注释、公共表表达式、嵌套查询和多语句,简单字符串检查既会误拦,也可能漏过危险构造。应使用 PostgreSQL 方言解析器生成抽象语法树,再执行白名单规则。

最低规则包括:只允许单条只读查询;拒绝 INSERT、UPDATE、DELETE、COPY、DDL 和扩展函数;只允许语义目录中的表与字段;禁止访问系统目录;必须有结果行上限;限制连接数量、子查询深度和高风险函数。

审查器返回的不只是通过或拒绝,还应给出机器可读原因,例如 TABLE_NOT_ALLOWED、MISSING_LIMIT、FUNCTION_DENIED。模型可以依据原因重写一次,但重试次数必须有限,不能形成无限生成循环。

第四道闸门:用数据库权限兜底

应用层审查永远可能有缺陷,所以执行账号必须是只读低权限角色,只能访问专门的分析视图。不要复用业务服务账号,更不要给超级用户权限后靠提示词写“禁止修改”。

PostgreSQL 侧至少设置语句超时、锁等待超时、只读事务和资源隔离:

BEGIN READ ONLY;
SET LOCAL statement_timeout = '3s';
SET LOCAL lock_timeout = '500ms';
SET LOCAL idle_in_transaction_session_timeout = '5s';
-- 执行审查通过且带 LIMIT 的查询
COMMIT;

结果行数限制并不能控制扫描量。一条只返回十行的查询仍可能全表排序。执行前可用 EXPLAIN (FORMAT JSON) 读取估算成本,超过阈值就拒绝或转入离线任务。估算不是绝对准确,但比毫无预算直接执行更可控。

第五道闸门:结果也需要验证

查询成功不代表答案可信。结果层应检查空结果、异常大值、单位、时间覆盖和列语义。若用户问“增长率”,SQL 只返回本期金额,就不能让模型在缺少基期时编造结论。

展示时保留三个可追溯对象:用户原问题、系统确认后的结构化意图、最终 SQL。对普通用户可以隐藏复杂细节,但审计与问题反馈必须能还原路径。模型生成的文字摘要应引用结果列,不得引入查询中不存在的数字。

Spring Boot 中的组件边界

建议把流程拆成明确接口:IntentParser 负责意图,SchemaRetriever 选择语义目录,SqlGenerator 只生成候选,SqlPolicy 解析与审查,QueryExecutor 在受限连接上执行,ResultVerifier 检查结果,AuditStore 保存必要元数据。

这些组件不要共享一个“万能 Map”。输入输出使用不可变数据类型,显式区分原始问题、候选 SQL、已批准 SQL 与执行结果。只有 ApprovedQuery 能进入执行器,类型边界能减少绕过审查器的偶然调用。

远程模型调用不要占着数据库连接。先完成意图与生成,审查通过后再从只读连接池取连接,执行结束立即释放。否则模型网络抖动会耗尽连接池,即使数据库没有慢查询,系统也会失去服务能力。

建立一套不会被演示数据宠坏的评测集

评测不能只有“能否生成目标 SQL”。至少覆盖五类样本:明确问题、缺少口径的问题、无权限字段、危险操作诱导、性能陷阱。每个样本记录预期行为是执行、追问还是拒绝。

对可执行样本同时衡量:语义结果是否正确、策略是否放行、实际结果是否与基准一致、执行成本是否在预算内。两个写法不同的 SQL 可能结果等价,因此不能只做字符串比对。

把线上失败去除敏感信息后回流到评测集。每次更换模型、提示词、语义目录或审查规则,都跑同一套回归。Text-to-SQL 的质量是整个管线的属性,不是某个模型排行榜分数。

审计日志要能查问题,但不能复制数据

建议记录请求 ID、用户与角色、意图摘要、口径版本、模式版本、SQL 哈希、策略结果、执行耗时、扫描估算、返回行数和错误类型。原始结果集通常不应完整进入日志,敏感参数也要脱敏。

当某条查询被拒绝时,日志应回答是权限、语法、成本还是口径问题;当用户报告数字不对时,能够重放当时的语义目录和查询,而不是拿今天的表结构猜昨天发生了什么。

上线前的最低标准

  1. 模糊口径会追问,不会静默猜测。
  2. 模型只看到与请求相关的最小模式。
  3. SQL 使用方言解析器审查,而不是正则或关键字替换。
  4. 执行账号只读,且仅能访问授权视图。
  5. 设置语句、锁和空闲事务超时,并限制结果行数。
  6. 高成本查询能在执行前被拒绝或转离线。
  7. 结果中的每个结论都可追溯到实际列值。
  8. 评测集覆盖追问、拒绝、权限和性能场景。
  9. 日志足以审计,但不保存完整敏感数据。

总结

可靠的 Text-to-SQL 不是“更长的提示词”,而是五道相互兜底的闸门。先把问题变成受控业务语义,再限制模型看到的模式,用结构化策略审查 SQL,用数据库低权限和资源预算限制执行,最后验证结果是否足以支持回答。模型可以犯错,系统的职责是让错误在到达生产数据库之前变得可见、可拒绝、可追踪。

更多推荐