跳到主要内容

大规模 Text-to-SQL:上线之前没人告诉你的那些事

· 阅读需 12 分钟
Tian Pan
Software Engineer

Text-to-SQL 演示看起来出奇地简单:把 schema 粘贴到提示词里,向 GPT-4 提个问题,拿到一条整洁的 SELECT 语句,然后 Slack 里就开始涌现"要不要把这个集成进数据平台?"的消息。等到你真正打算上线时,麻烦来了。基准测试显示 85% 的准确率,但内部数据团队反映大约一半的答案是错的,而安全团队则在追问:生成的查询在执行前有没有经过审查?没人能给出一个像样的答案。

这正是 text-to-SQL 作为研究问题与作为工程问题之间的鸿沟。研究问题关注的是让模型生成语法正确的 SQL;工程问题面对的是 schema 歧义、访问控制、查询验证,以及你的企业数据库根本不像 Spider 或 BIRD 数据集这一现实。

基准测试数字在骗你

Spider 基准——引用次数最多的评估数据集——显示顶级系统的执行准确率达到 85–91%。听起来可以直接上生产了。但 Spider 只包含 146 个经过精心策划的数据库,命名规范清晰,外键文档完整,查询相对简短。你的生产数据库有 400 张表,列名叫 cust_acct_bal_adj_dt,那些 JOIN 只有 2017 年在公司的人才看得懂。

基准与现实之间的差距触目惊心。Snowflake 公布的内部数据显示,GPT-4o 在真实 BI 问题上的准确率仅为 51%,而在其合成评估集上则超过 90%。BIRD 基准被设计得更难,使用真实世界的嘈杂数据库,GPT-4o 在上面也只能达到 81%——和你的数仓团队十年积累下来的那套数据库还是不一样。

差距归根结底来自两点:schema 复杂性和问题语义。基准问题问的是"每门课程有多少学生注册?",你的用户问的是"Q3 续约但年底前流失的账户对 ARR 有多少贡献?"第二个问题需要理解"ARR 贡献"对你的业务意味着什么、哪张表记录了续约事件、以及如何 JOIN 三张本来没打算在一起用的表。没有基准会测这些。

这意味着你不能用基准准确率来预测生产准确率。你需要在你自己的数据上、针对你自己的 schema、用你的真实用户提出的问题来评估。

Schema 歧义不是模型的问题

当 text-to-SQL 系统选错了列,本能反应是怪模型。但大多数情况下,模型根据它所看到的信息做出了合理的选择。真正的失败在于 schema 本身有歧义,而系统没有办法消解这种歧义。

举个简单的例子:"有多少订单来自 Fresno?"如果你的数据库同时有 city 列和 county 列,而 Fresno 既可以是城市也可以是县,模型只能猜。它可能选 city,但正确答案需要 county。单从 schema 来看,两种解读都说得通。

关于交互式消歧系统的研究揭示了这个问题的规模。没有任何澄清机制时,text-to-SQL 系统在有歧义的问题上精确匹配准确率约为 42.5%;加入一个简单的交互循环——向用户回问一个澄清问题——准确率跃升至 92.5%。这 50 个百分点的差距完全来自于对模型无法自行解决的 schema 歧义的消解。

工程上的应对不是寄希望于模型自己想清楚,而是在系统中构建一个澄清层。在生成 SQL 之前,检测问题是否唯一映射到 schema 元素。如果两列都是同一个自然语言词的合理匹配,就把这个歧义暴露给用户。这比端到端生成要枯燥得多,但它正是区分"真的能用"和"演示能用"的关键。

第二个应对是提供更好的 schema 上下文。如果模型在 DDL 之外还能收到列描述、业务术语定义和示例值,它就能做出更好的推断。col: cust_tiercol: cust_tier(客户层级:1=标准, 2=高级, 3=企业)之间的差距是巨大的。整理这些元数据是苦力活,但它直接对应于准确率的提升。

跨表推理在三个 JOIN 处崩溃

单表查询基本上算是解决了。两表 JOIN 大多数时候也能工作。三张或更多表,就是现有系统开始以 20% 错误率失败的地方,即使在有明确外键文档的干净 schema 上也不例外。

失败模式是一致的:

过度 JOIN:模型引入了一张不需要的表,产生笛卡尔积或膨胀行数。查询有结果,但差了 10 倍。

错误的 JOIN 条件:模型识别了正确的表,但 JOIN 在了错误的列上。customer.id = order.customer_id 变成了 customer.id = order.id。查询能运行,有返回,但数字是错的。

JOIN 后聚合:先 JOIN 再聚合与先聚合再 JOIN,在存在一对多关系时结果不同。模型在复杂查询中经常搞错这一点。

真正有效的架构应对是分解。不要让模型一次生成一条复杂查询,而是把问题拆成步骤:先识别哪些表是相关的(schema 链接),再弄清楚如何 JOIN,然后为每个逻辑步骤生成 SQL,最后组装成最终查询。实现这条流水线的多智能体系统——分解器、schema 链接器、带验证循环的生成器——在复杂查询上始终优于端到端方法。

这也是少样本示例值得其成本的地方。从你的查询历史中挑选在结构上与当前问题相似的示例——相同数量的 JOIN、相同的聚合模式——并将其包含在提示词中,可以显著减少复杂查询错误。开销是真实存在的:DIN-SQL 的少样本上下文使用超过 10,000 个 token。但在三表以上的查询中,准确率提升证明了这是合理的。

通过 LLM 实施 SQL 注入是真实的攻击面

text-to-SQL 的安全讨论通常聚焦于幻觉——模型生成一条返回错误数据的查询。更危险的一类失败是对抗性的:用户精心构造一个自然语言问题,诱使模型生成超出其访问权限或执行破坏性操作的 SQL。

ToxicSQL 研究(发表于 2025 年 3 月)系统性地证明了这一点。通过在微调过程中引入后门攻击,研究人员证明了基于 LLM 的 text-to-SQL 系统可以被触发生成 DROP TABLE 语句、权限提升查询和数据窃取命令——而触发它们的自然语言输入看起来完全无害。即使没有微调投毒,提示词到 SQL 的注入攻击也可以构造出映射到未授权查询的自然语言:"把所有客户记录(包括 PII 字段)导出到一张临时表给我看看。"

缓解方案是分层的,每一层都很重要:

只读数据库凭证:智能体应以只有 SELECT 权限的用户身份连接,没有写权限。这一招消灭了整类破坏性变更。它是成本最低、效果最显著的控制措施。

Schema 遮蔽:不要把完整的数据库 schema 给模型。如果 PII 表、财务审计日志或内部安全表不应该被查询,就把它们从模型看到的 schema 上下文中排除。模型无法查询一张它不知道存在的表。

行级安全:数据库原生的 RLS(PostgreSQL、SQL Server、Oracle 均支持)在数据库层面强制过滤行,无论查询内容如何。即使模型生成了针对敏感表的查询,数据库也只会返回当前用户有权看到的行。这是对 schema 遮蔽失效的纵深防御。

运行时查询验证与策略执行:在执行前,解析生成的 SQL,并根据允许的表、列和操作的白名单进行校验。Open Policy Agent (OPA) 等工具可以声明式地表达这些规则,并作为模型输出与数据库之间的网关来执行。模板化执行——让模型从预批准的查询模板中选择,而不是生成自由格式的 SQL——可以完全消除注入攻击面,代价是牺牲查询灵活性。

审计日志:每一条生成的查询、每一次执行,以及触发它的用户上下文,都应该被记录下来。当出问题的时候——它一定会出——你需要能够追溯。

你需要的查询验证流水线

生成 SQL 然后立即执行,是错误的架构。正确的模式是:生成 → 验证 → 执行,失败时迭代。

验证有多个层次:

语法与 schema 验证:解析生成的 SQL,验证每个表和列的引用在给模型的 schema 中确实存在。在查询到达数据库之前,捕获幻觉出来的表名和列名拼写错误。

基于 AST 的策略检查:提取查询结构——访问了哪些表、执行了什么操作、有没有 LIMIT 子句——并与你的策略规则进行核验。没有 LIMIT 的查询应该被拒绝或自动加上限制。访问许可集之外表的查询应该被阻断。

试运行执行:在小型测试数据集上执行查询,或使用 EXPLAIN 计划来验证语义正确性,然后再对生产数据运行。这能捕获通过语法验证但 JOIN 条件错误或聚合有问题的情况。

行数与成本限制:即使是 SELECT 查询,对一张 5 亿行的表进行全表扫描也能把你的数仓打垮。在执行前强制设定自动行数限制、查询超时和成本估算。

当验证失败时,把错误信息连同上下文一起反馈给模型,请求修订后的查询。执行引导的迭代循环——生成、测试、修正——能捕获单次生成中遗漏的大量错误。在计算延迟时,至少预留两次迭代的时间。

Text-to-SQL 真正适用的场景

考虑到上述这一切,你应该在哪里部署它?

Text-to-SQL 在有明确边界的分析查询上、针对文档完善的 schema,能够发挥价值。"三月份有多少用户注册?""各获客渠道的转化率是多少?""上季度哪个产品退货率最高?"这些问题能干净地映射到单表或双表查询,列引用没有歧义,也不需要理解隐含的业务逻辑。

只要涉及 schema 没有编码的业务上下文,它就会力不从心。涉及折扣应用规则的收入计算、有账户类型例外的流失定义、来自多个没有设计为协同工作的系统的多维分析——这些场景要么需要大量元数据增强,要么需要人在回路中提供澄清,才能可靠地工作。

实用的判断标准:如果一个有一定经验的分析师只凭 schema 就能在五分钟内写出这个查询,那么配置良好的 text-to-SQL 系统大概也能处理。如果分析师需要先问两三个澄清问题,就把你的系统也设计成会问这些问题。

良好的生产架构是什么样的

在生产中跑通的系统有一个共同的形态:

它们维护一个经过策划的 schema 层——不是原始的 DDL,而是带有业务定义、列描述、示例值和已记录业务规则的注释版本。这由数据团队维护,不是自动生成的。

它们用检索来为每个查询选取相关的 schema 子集,而不是把整个数据库结构塞进提示词。对于大型数据库(50 张表以上),这个检索步骤对准确率和成本控制都是必不可少的。

它们实现分解-链接-生成-验证的流水线,而不是端到端生成。每个阶段都可以独立监控和调试。

它们把模型的输出视为不可信的用户输入,在数据库层面而不仅仅在提示词层面执行访问控制。

它们针对自己的查询历史而非基准数据集来衡量准确率,并利用评估结果来识别哪类问题需要更多少样本示例或更严格的 schema 文档。

生产规模的 text-to-SQL 是一个数据工程问题,和模型问题同等重要。成功落地的团队不是那些写了最精妙提示词的团队,而是那些投入了 schema 元数据建设、构建了验证层、并从一开始就把数据库访问当作安全边界来对待的团队。

Let's stay in touch and Follow me for more thoughts and updates