
这两年我看了不少 Text-to-SQL 的演示。台上的人随口说一句“帮我查一下华东区上个月各产品的销售额”大屏上 SQL 刷刷刷生成表格图表跟着就出来了底下人鼓掌。可你要是追问一句真实环境里准确率多少跑一次要多少秒用户换一种问法还认不认得出来系统怎么知道自己生成错了大多数演示者都会顿一下。热点里搜“demo”大家关心的是怎么把 demo 程序跑起来、怎么去掉 demo 水印这恰恰说明一个行业现实Text-to-SQL 这玩意大家手里拿的几乎都是 demo。无论是学术项目、开源框架还是企业内部孵化能走到生产环境、被业务方天天稳定使用的少之又少。这不是某一家的个例而是整个赛道普遍存在的现状。这篇文章我想拆一拆到底卡在哪里。会结合我自己在真实数据环境里做落地的观察把那些 demo 里不会告诉你的坑一个一个摆出来。1. Demo 为什么看起来很美1.1 各路 demo 都在演什么GitHub 上找个开源的 Text-to-SQL 项目跑起来都不难。装好依赖、导入示例数据库、启动页面然后你在输入框里问一句“有多少个用户”SQL 出来了结果也出来了。体验顺滑得像是在用魔法。但你要是仔细看这些 demo会发现它们有几个共同点数据库很小几张表字段命名清清楚楚问题很简单基本是单表过滤加聚合用户在演示前早就把各种问法试过一遍知道哪些话模型能答对。说白了这些 demo 的本质是“在受控环境里展示模型能力”它在展示的是上限不是常态。真正的生产环境恰恰相反业务方的提问五花八门表有几十上百张字段名你可能看一眼都想骂人模型面对的不是精心挑选的问题而是真实的、随机的、充满了歧义的自然语言。1.2 藏在 Demo 后面的“主语缺失”大多数 demo 都没回答一个问题谁在用、用它解决什么问题、错了怎么办。举个例子。演示的时候用户问“北京地区的手机销量是多少”数据库里正好有 region 字段、有 category 字段、有 sales 字段模型一次答对。但真实用户会问“北京卖得怎么样”“北京这边手机走得快不快”“帮我看看北京手机的情况”——这些表达里有省略、有指代、有口语模型未必能准确映射到字段。更麻烦的是真实数据库里字段名可能是 f01、f02 这种或者叫 product_type、sku_category、item_category你以为的“手机”和数据库里的“手机”根本不是同一个词。我见过不止一个项目demo 阶段准确率看着有 80% 多一接到真实业务库准确率直接掉到 30% 以下。原因很简单演示数据是干净的、准备好的真实数据是脏的、混乱的、充满历史包袱的。这也是“为什么 Text-to-SQL 总是停在 Demo”的第一个答案——很多团队低估了真实数据和演示数据之间的差距。1.3 演示环境和生产环境的评价体系完全不同在 demo 里生成一条 SQL 就算成功在生产环境里这个标准远远不够。把两边的评价指标放在一起看差异非常明显维度Demo 环境生产环境数据规模几张表几千行几十上百张表上亿行Schema 质量命名规范、注释完整缩写、无注释、历史遗留字段问题形式经过筛选的提问口语化、省略、歧义多正确性跑出结果即可必须和业务口径一致错误处理演示前多次试错要能感知错误、提示用户安全权限不考虑行级权限、表权限、审计性能毫秒级响应要控制超时、资源和成本表格列出来就能发现大家天天谈的 Text-to-SQL在 demo 里是“Text 到 SQL 的翻译问题”在生产里是“自然语言到可靠数据服务的系统工程问题”。后者远不是一个模型能独立解决的。模型只是中间一环前后全是工程。2. 第一个深坑Schema 绑定比想象中难得多2.1 LLM 是语言模型不是数据库模型很多人以为把数据库结构往提示词里一塞大模型就能像 DBA 一样精准理解数据库。这个想法低估了一件事大模型训练的时候没有见过你的数据库。它见过海量的自然语言和代码但它不知道你这个库里的 sales_order 表到底是“销售订单”还是“销售目标”。字段名背后的业务含义模型只能靠猜。比如 create_time 和 order_date 到底哪个是下单时间status 字段里的值到底是 1 表示成功还是 2 表示成功这些信息在数据库里往往只存在于业务文档或者老员工的脑子里模型根本不知道。所以在 Text-to-SQL 落地里最核心的不是模型能力而是“Schema 绑定”——把用户的自然语言问题准确绑定到物理表、物理字段和字段值上。这一步错了后面生成的 SQL 结构再优美都是垃圾。2.2 字段和表名不规范化一切白搭我在真实项目里见过不少“原生态”的表结构。有表名直接叫 a、b、c 的有字段叫 col1、col2 的有中英文混杂的有同一含义字段在不同表里叫不同名字的。拿这种 schema 去喂大模型你还指望它能理解它连哪个字段是日期、哪个字段是金额都分不清。做 Text-to-SQL 落地的第一件事永远不是调模型而是整理数据字典。具体来说至少要做这几步给每张表写清楚业务注释比如“销售订单主表一行代表一个订单”给每个字段写清楚含义、取值范围、示例值比如“status订单状态1-待支付2-已支付3-已取消”把同义词关系维护起来比如“客户”和“customer”是同义的“下单时间”和“order_time”是同义的处理历史遗留命名能改就改不能改就在语义层做映射这一步做完模型才具备理解数据库的基本前提。不做这一步后面所有优化都是空中楼阁。2.3 Schema Linking 才是真正的瓶颈当表少的时候把整个 schema 塞进提示词模型基本能应付。但生产库一上来就是几十张表几百个字段提示词根本塞不下塞下了模型也“看不完”。这时候就牵扯到 Text-to-SQL 领域里一个专门的问题Schema Linking也叫“表结构链接”。说白了就是先从几十张表里挑出和用户问题相关的三五张表再从这几张表里挑出相关的字段把无关信息全部过滤掉再交给模型去生成 SQL。这个环节做得不好模型会在不相关的表里找字段甚至根据字段名的字面相似度乱关联。我见过最典型的错误是用户问“今年新签了多少客户”模型不看客户表的 sign_date 字段反而去订单表里找了个 create_date 字段把订单创建时间当成了新签时间。结果 SQL 语法完全正确执行也不报错数字却完全不对。这种错误在 demo 里很少出现因为演示库表少、字段少模型不容易选错。真实场景下Schema Linking 的准确率直接决定了系统上限。实操中比较有效的做法是组合拳先用规则和向量检索召回候选表再做一次粗排最后在提示词里只保留候选表的完整信息。这个环节值得花大力气优化它的收益比换一个更大的模型要明显得多。3. 第二个深坑只做生成不做校验和兜底3.1 生成之后的 SQL 修复才是真正的防线很多团队做 Text-to-SQL把目光全放在“生成”这一步优化提示词、微调模型、做 few-shot目的都是让模型生成更准的 SQL。但生成完之后的环节几乎没人认真做。这是第二个大坑。我实测下来一个正常的大模型生成的 SQL直接丢到数据库里执行一次性成功的比例并没有想象中高。表名不存在、字段名拼错、函数用错、类型不匹配、join 条件写反各种低级错误层出不穷。更麻烦的是这些错误很多时候不是语法错误数据库执行时不会报错结果却是错的。所以一个合格的 Text-to-SQL 系统生成 SQL 后面必须跟一道完整的校验流水线语法解析用数据库方言的 parser 解析 SQL发现语法错误直接打回Schema 校验把 SQL 里的表名、字段名和真实 schema 比对发现不存在的对象直接打回执行计划检查用 EXPLAIN 看 SQL 的执行计划识别明显的笛卡尔积、全表扫描结果规则校验跑完以后检查结果集的行数、字段数是否合理这一步相当于给模型加了一道安全网能把很大一部分低级错误拦截在用户看到结果之前。可惜的是绝大多数的 demo 项目根本没有这个环节模型生成什么就直接执行什么看起来“智能”实际上是在碰运气。3.2 权限、血缘、数据安全在生产中是硬指标demo 里不会有人关心权限。但真实企业环境里这是红线。一个业务人员通过自然语言查询数据他只能看自己有权限看的数据不能因为模型生成了跨表 join就把敏感数据暴露出去。这意味着 Text-to-SQL 系统不能只做“翻译”还要把权限模型嵌进执行链路里。表级权限相对好办行级权限比较麻烦。比如销售只能看自己负责区域的数据那系统在生成 SQL 时就必须自动拼上 region_id 当前用户所属区域 这样的过滤条件。这个逻辑如果放在提示词里让模型“自觉”基本不靠谱最稳妥的方式是在执行层强制注入。另外还有审计和血缘的需求。谁在什么时候、问了什么问题、系统生成了什么 SQL、返回了什么结果这些日志必须留全。一方面是为了出问题的时候能追溯另一方面是基于这些日志做后续的反馈迭代。很多团队忽略这一点等系统上线跑了一个月想优化却发现连历史 badcase 都捞不出来。3.3 值得先定“能跑”的边界我见过不少项目卡在“想解决所有问题”上。团队希望 Text-to-SQL 什么都能问结果什么都不敢保证。做生产系统恰恰相反一上来就要画清楚边界哪些查询用 Text-to-SQL 来做哪些查询不做。比如简单的事实查询、单表筛选、聚合统计这类问题适合让模型直接生成 SQL但复杂的多步骤分析、需要业务判断口径的问题就不该硬让模型生成可以引导用户去找报表或者直接转给数据分析师。边界画清楚以后系统的准确率会好做很多用户预期也更容易管理。demo 里“什么都敢答”的潇洒在生产里就是事故隐患。4. 第三个深坑复杂查询和业务语义4.1 多表 join 与嵌套查询如果说单表查询是 Text-to-SQL 的舒适区那多表 join 就是分水岭。模型要自己判断哪张表和哪张表关联、用哪个字段关联、一对多会不会产生重复数据。这些对于老 DBA 来说是直觉对模型来说是纯粹的盲猜。一个真实案例。用户问“每个品类的销售额是多少”数据库里有订单表、订单明细表、品类表。最直接的关联逻辑是订单明细表通过 product_id 关联品类表然后按品类分组汇总金额。但模型也可能直接拿订单表的 product_category 字段去分组如果订单表里这个字段其实是冗余存储的商品一级分类结果看似合理实则口径不同。更隐蔽的问题是 join 条件写错导致结果翻倍比如订单表和明细表是 1 对 N 的关系没有先去重就直接 join销售额一下子被放大好几倍。这种数字错误用户一眼就能看出来不对但系统自己很难发现。所以在实际项目里多表场景我不太建议完全交给模型自由发挥。更务实的做法是提前把常用关联关系固化下来形成“关系图谱”让模型在图上做路径选择而不是从空白的宏大的 SQL 语法空间里凭空生成。4.2 业务口径SQL 里没有的“潜规则”Text-to-SQL 真正难的点很多时候不在 SQL 本身而在于业务口径。同一个指标在不同人嘴里可能完全是两个含义。比如“销售额”是含税还是不含税退款算不算已经取消的订单要不要排除“客户数”是按自然人去重还是按公司去重这些口径在业务讨论会上都要吵半天你指望模型自己从字段名里悟出来不太现实。所以成熟的架构里会加一层“语义层”。把业务指标先定义好比如“有效销售额 订单状态为已支付且未退款的订单金额合计”然后在生成 SQL 时引导模型基于这些预定义的指标去生成而不是直接对裸表操作。用户问“有效销售额”系统把它映射到已经写好的指标定义上再展开成具体的 SQL。这样做既能保证口径一致也大大降低了模型自由发挥的空间。说得直白一点想尽办法缩小模型的“创作空间”把关键路径都变成填空和选择它就不容易出错。4.3 性能问题SQL 能跑不等于能上线生成一条 SQL 能跑出正确结果和这条 SQL 能在生产环境跑完是两码事。真实数据量一上来性能问题立刻暴露。模型生成的 SQL 经常出现这种情况逻辑上挑不出毛病但执行计划极其糟糕。比如该走索引的时候走了全表扫描该用聚合的地方先取了几百万行明细再在应用层汇总或者莫名其妙做了个笛卡尔积。数据库一跑就是几分钟把生产库的资源都吃光了。我建议所有 SQL 在执行前都要过一道 EXPLAIN检查有没有代价特别高的节点超过阈值直接拒绝执行或者改用其他写法。同时还要给查询设置超时时间、限制返回行数、限制单个查询可以扫描的表数量。这些在 demo 里完全不用考虑但在生产环境里每一条都是救人命的关键配置。5. 工程化才是从 Demo 到产品的分水岭5.1 Text-to-SQL 不是模型是系统如果你只把 Text-to-SQL 当成一个“模型能力”问题那你大概率会一直停在 demo 阶段。真正的生产级 Text-to-SQL是一个完整的系统模型只是其中一个组件而已。我理解的最小可用系统至少包含这些模块自然语言解析识别查询意图、抽取核心条件、Schema 链接召回候选表和字段、SQL 生成模型生成候选 SQL、校验与修复语法、语义、执行计划三道关卡、执行与结果解释安全执行、返回结果并附上生成过程说明、反馈采集记录用户的后续操作和评价。这个链路里模型做得再好前面没有 schema 召回、后面没有校验兜底整体依然不可用。反过来前面的召回和后面的校验做扎实了模型反而没那么关键——随便换一个开源模型准确率也不会差太多。5.2 反馈闭环从用户真实操作里学习静态的模型加提示词上线第一天就是能力上限。想要持续变好必须建反馈闭环。最简单的方式是在界面上加一个“这个结果是否有用”的按钮再把用户的后续行为记录下来他有没有修改生成的 SQL他有没有换个说法重新问他有没有直接复制结果走人把这些行为和数据串起来每一条都是一条训练样本。积累一定量之后拿出那些用户反复修改、反复提问的 badcase分析是 schema 映射问题、口径问题、还是模型生成问题然后针对性优化。有的放矢地积累案例库比盲目换模型、堆提示词有效得多。我在实际项目里一个很深的体会是Prompt 里的 few-shot 示例质量远比数量重要。放五个贴合真实业务的 badcase 示例比放二十个教科书式的通用示例效果好得多。5.3 评测集不要迷信公开数据集网上很多开源评测集比如 Spider、Spider 2.0 这些在学术界很有影响力。你要是拿它们来评估模型能力没有问题但你要是拿它们来预测你的系统在真实业务里好不好用那差距就大了。因为公开数据集里的数据库结构和问题类型跟你企业内部的真实情况可能完全是两回事。务实的做法是自己搭一套领域评测集。从真实的用户 query 里抽出一批有代表性的问题手工标注标准答案再覆盖重点场景单表查询、多表 join、时间范围筛选、指标口径、模糊表达等。评测维度也不要只看“生成 SQL 是否和标准答案一致”更建议看“执行结果是否一致”。因为同一个问题正确的 SQL 写法可能有多种结果一致即可算对。再辅以平均延迟、超时率、无结果率这些工程指标才能相对全面地反映系统状态。没有评测集你连自己的系统是在变好还是变坏都无法判断这就没法谈产品化。6. 从 Demo 到落地人和流程同样重要6.1 设计好交互别让用户干等很多 Text-to-SQL 系统接上大模型就开始给人用了结果用户一问系统吭哧吭哧想了五秒然后生成一条 SQL用户也不知道它思考得对不对只能像拆盲盒一样点“执行”。这种做法把产品体验做成了抽奖。好的交互一定要有“过程透明”。用户提问之后系统应该展示自己理解了哪些条件你问的时间范围是哪一段你指的“华东区”对应哪些地区你在说的“销售额”是含税还是不含税让用户在执行前有机会确认或者纠正。遇到歧义的时候不是猜一个答案而是主动反问。比如用户问“这个月的销售额”系统应该确认“本月是指自然月 2024 年 1 月对吗”这种交互带来的准确率提升比任何模型优化都直接。6.2 领域适配层把模型“钉”在业务上大模型是通用的而你的业务是特殊的。两者之间需要一个适配层。这层可以是业务词表、指标字典、同义词映射也可以是一组常用的查询模板。举个例子用户说“新签客户数”如果没有适配层模型得自己去猜“新签”对应哪个字段。有了指标字典以后系统直接知道“新签客户数”对应的口径是“客户表的 sign_date 在查询时间范围内且 status 不等于取消”模型只需要基于这个定义去生成具体 SQL。用户要是问“最近新签的客户有多少”通过同义词映射也能关联到“新签客户数”这个指标。这样做的意义是把模糊的自然语言先转成一套中间表示再转成 SQL。中间表示相当于给模型铺了一条路它不容易跑偏。6.3 验收时要设计“幻觉测试”Text-to-SQL 的幻觉和聊天机器人的幻觉不太一样。聊天机器人瞎编一段话用户可能听不出来Text-to-SQL 如果瞎编了一个字段名数据库执行会直接报错这个倒是容易发现。更难防的是“看起来合理的幻觉”字段名存在、语法正确、结果也有但字段代表的含义跟用户问的根本不是一回事。所以我建议验收的时候专门设计一批“幻觉测试”用例。故意问一些库里根本不存在的指标问一些容易产生歧义的词问一些需要复杂业务判断的问题看系统会不会“硬答”。一个负责任的系统在拿不准的时候应该说“我找不到对应的数据字段请你换个说法”或者“这个指标我不确定建议参考 XXX 报表”。敢说“不知道”比硬给一个错误答案要高级得多。这也是从 demo 走向生产最重要的心态变化。7. 常见问题与排查技巧实录7.1 常见问题速查表我把做 Text-to-SQL 落地时最常遇到的一批问题整理了一下给正在做相似方向的朋友一个参考现象可能原因解决建议SQL 报字段不存在Schema 信息没有完整传给模型检查提示词里的 DDL 是否完整或者校验层是否拦截执行成功但结果明显错误表或字段选错、join 关系错了加强 Schema Linking把候选表的范围缩小用户换个说法就问不出来依赖关键词匹配缺少同义词映射建同义词词表积累用户真实说法同一个指标不同人结果不同业务口径没有固定建语义层把指标定义固化下来查询特别慢生成了全表扫描、笛卡尔积加 EXPLAIN 检查设置超时和资源限制模型生成的 SQL 时好时坏Few-shot 示例不贴合业务用真实 badcase 替换通用示例用户觉得系统“傻”交互太单薄缺少确认和解释加澄清交互展示理解结果7.2 几条实操心得最后说几条我自己踩坑踩出来的经验。第一不要一上来就微调模型。大多数项目的瓶颈不在模型能力而在 schema 没整理干净、badcase 没积累、评测集没建立。先做工程兜底和提示词优化效果往往比微调明显得多。第二从“窄而准”的场景起步。先挑三张最核心的表把字段含义、关联关系、常用口径全部梳理清楚把场景做深做透再逐步扩张。一个只能查三张表但准确率 95% 的系统比一个能查三十张表但准确率 50% 的系统有价值得多。第三每个 badcase 都不要放过。用户问不出来的问题、生成错的结果、执行超时的 SQL这些都是最宝贵的优化素材。每周固定时间复盘一次把 badcase 归类整理你会发现系统的进步是肉眼可见的。第四也是最重要的一点Text-to-SQL 的本质不是“让大模型理解数据库”而是“把数据库整理成让模型能理解的样子”。模型是现成的你的数据不是。功夫花在模型之外系统才能真正从 demo 走向生产。如果让我给准备做这类系统的人一句总结我会说先别急着上大模型先把你的数据字典整理出来先挑几张核心表做深做透先让业务方用起来、骂起来然后再谈扩展。我见过太多团队把期望寄托在模型本身最后才发现真正决定成败的都是模型外面那一圈不起眼的工程活。