ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

NL2SQL效果差?先给Schema降个噪:从静态裁剪到动态选择的实战指南

NL2SQL效果差?先给Schema降个噪:从静态裁剪到动态选择的实战指南

干过NL2SQL落地的人应该都有一种体会:模型效果不好,八成不是模型的问题,而是喂给模型的东西太“脏”了。尤其在处理企业级数据库的时候,一张宽表动辄上百个字段,一个业务库几百张表,把这些schema原封不动塞给大模型,注意力早就被稀释完了,SQL生成质量自然肉眼可见地往下掉。这就是NL2SQL里常说的“噪声”——而这里面占比最大、杀伤力最强的噪声源,就是schema本身。

这个方向前前后后折腾了大半年,中间踩了很多坑,也沉淀了一套相对成熟的降噪思路。核心就一句话:不要试图让模型理解整张数据字典,而是想办法让它只看到它该看到的那一小块。围绕这点,我整理了一整套schema简化的方法体系,从噪声拆解、简化思路到具体落地步骤和排查技巧,尽量讲透。如果你正在做NL2SQL相关的工程化落地,或者手里的模型在复杂业务库上效果始终上不去,这篇文章应该能帮你打开思路。

1. Schema噪声到底从哪来:三类噪声的拆解与认知

在做任何简化之前,你得先搞清楚schema里到底哪些东西在干扰模型。我习惯把schema噪声分成三类:结构性噪声、语义性噪声和上下文性噪声。这三类的成因、表现和解决思路完全不同,混在一起谈只会越谈越糊涂。

1.1 结构性噪声:表、列、类型的信息爆炸

结构性噪声是数量层面的问题。一个真实的业务库,多少个表?几百张很正常。每张表多少个字段?几十个起步,宽的能上百。你要是把这张全量schema拼进prompt里,token数轻松上万,甚至有朋友遇到过几十万token的场景。这种量级,先不说模型上下文窗口顶不顶得住,光是推理延迟和成本就已经不可接受了。

更隐蔽的问题是:这些字段里,绝大多数跟用户当前的问题毫无关系。用户问“上个月销售额是多少”,你给他200个字段,真正相关的可能就一个order_amount。对模型来说,这200个字段里的一部分,本身就是噪声——它们会吸引注意力、制造歧义、干扰选择。有公开资料显示,在复杂schema上,模型生成SQL的准确率会随着无关表数量的增加呈明显下降趋势。这个结论我在自己的实验里也复现过:同样是问“哪个客户的订单量最大”,只给2张相关表时准确率接近九成,给全库20多张表时直接掉到六成左右。

另外,字段类型、默认值、是否为空这类元信息,对模型理解语义帮助有限,但占用的token不少。varchar(255)、decimal(18,2)这种信息,模型并不需要每次都看到。所以结构性噪声的本质是“信息过载 + 注意力稀释”,解决思路也很直接:能不给的就不给,能少给的绝不多给。

1.2 语义性噪声:命名混乱与注释缺位

如果说结构性噪声是“多”的问题,语义性噪声就是“糊”的问题。

企业库里的字段命名,什么风格都有:a001、b_2、data_flag、user_name2,还有一堆拼音缩写和英文混搭。有些表甚至没有注释,字段含义全靠猜。你把这些schema喂给模型,等于让它做一道没有题干的阅读理解题。模型不是神,它没法自然而然地知道status字段里1代表已支付、2代表已退款,除非你告诉它。

更麻烦的是同义异构问题。同一个概念,在不同表里叫法完全不一样:用户ID可能是user_id,也可能是uid、u_id、member_id。模型在生成JOIN条件的时候,看到这些长得不一样但语义相同的名字,很容易犯迷糊——它不知道这两个字段能不能连,于是要么瞎猜,要么干脆生成一个语法正确但逻辑错误的SQL。

解决语义性噪声,方向是做“语义增强”而非单纯做“减法”:补充注释、统一别名、显式标注字段的业务含义。很多团队只关注token压缩,忽略了语义侧的补全,这是常见的误区。降噪不等于砍信息,砍掉的是无关信息,该补的语义信息还是要补。

1.3 上下文性噪声:相似列、外键与Join链路的干扰

上下文性噪声是最难识别的一类,因为它藏得很深。

典型场景一:一张表里同时存在created_at和updated_at,问“这批订单什么时候创建的”,模型需要在这两个时间字段里选一个。对模型来说,这两个字段长得太像了,如果没有注释说明,它很容易选错。

典型场景二:跨表查询需要经过中间表。用户问“某商品的供应商联系方式”,商品表、供应商表和商品供应商关联表三张表,模型得“看懂”这个join链路才能生成正确SQL。如果schema只是平铺在那,没有外键关系、没有关联说明,模型只能在心里默默猜,猜错了就出问题。更别提在多对多关系里,链路不止一条,选错路径就是全错。

还有一种上下文噪声,来自“候选集的相似度”。当schema里存在大量结构相似的表(比如按月分表:order_202401、order_202402……)或者大量含义相近的字段(amount、total_amount、pay_amount、settle_amount),模型的决策难度会指数上升。这里的本质是:不是模型不够聪明,而是你给它的选择题太难了。上下文性噪声需要靠“关系显式化”和“候选集排序”来解决。

2. 降噪的核心思路:五条主线怎么选、怎么做

认知清楚了,接下来聊方案。我实践下来,schema降噪有五个大方向:静态裁剪、动态选择、语义增强、层级重组织、列级消解。它们不是互斥的,一套生产级方案通常是把它们叠加使用。下面逐个拆解。

2.1 静态裁剪:按业务域拆全局schema

静态裁剪是最朴素但非常有效的一步。思路是:把整个数据仓库按业务域拆成若干子schema,每个子schema只包含一个业务域的几十张表。构建NL2SQL系统的时候,先做一次业务域路由,再只喂对应域的子schema给模型。

举个例子,一家做电商业务的公司,数据仓库里可能有订单域、用户域、库存域、营销域、财务域。用户问“这个月新品上架了多少”,那就只喂库存域和商品域的schema,完全不碰订单域和财务域的表。“物理隔离”听起来很野蛮,但实测效果极好。这相当于人类程序员看代码的时候,你先告诉他去看哪个模块,他就不用通读整个项目了。

静态裁剪的关键动作有两个。一是盘点:把库里的表全部过一遍,搞清楚每张表属于哪个业务域、跟哪些表有关联。二是建索引:设计一张域路由表,记录表名、域标签、常用查询关键词。路由这一步可以用规则(比如用户问题里出现了“订单”就落到订单域),也可以用模型分类,或者用向量检索。工业界现在普遍的做法是“规则保底 + 向量召回”,简单可控,又有一定泛化能力。

静态裁剪要注意一个坑:跨域查询。用户问“上个月的退单率”,既涉及订单域的退款表,也涉及用户域的会员表。如果分域太死,跨域的查询就凉了。我的做法是允许schema有“交集区”,把高频跨域的表放进多个域的字典里,同时路由的时候支持多域命中,减少误伤。

2.2 动态选择:让问题决定看哪些表

静态裁剪解决了“几百张表”的问题,但一个业务域内可能还有几十张表、上千个字段。这时候就需要动态选择:每一次查询进来,根据问题内容,动态挑出最相关的一小撮表和字段。

动态选择的主流实现是“问题到Schema”的检索引擎。流程大致是:

  1. 对用户问题做解析:识别里面出现的业务词、数字、时间等信息。
  2. 基于解析结果,召回候选表:这一步同时用两种模式,关键词匹配(如果问题里有“订单”,直接命中订单相关表)和向量检索(把问题和schema描述都embedding化,算相似度)。
  3. 对召回结果做排序:取TopK张表进入最终prompt。

这里最容易翻车的点,是“关键表被召回、辅助表没被召回”。比如用户问“哪些用户没买过东西”,主表是用户表,但你要的是“不在订单表里出现过的用户”,辅助表订单表才是关键。这种问句里的隐性约束,纯靠关键词很难触发。我的经验是:动态选择之后必须加一步关联表补全——拿到TopK表之后,找出它们之间必要的JOIN关系,把缺失的中间表补回来,宁可稍微多给一张,也不要因为少了一张表导致整条SQL生成失败。

2.3 语义增强:减法之外的加法

很多人把降噪理解成“砍”,但真正专业的降噪还会做“加”。加什么?加让模型少猜的信息。

第一是字段注释补全。国内数据库的注释覆盖率普遍不理想,我见过不少表超过一半的字段没有注释。通过分析DDL里的字段名、索引、历史慢查询日志,甚至是样本数据,可以批量生成注释草稿,再经过一轮规则校验和人工抽检,形成一个相对可信的注释表。字段注释这块,目标不只是“有个解释”,而是要解释到位:含义 + 值域含义枚举 + 特殊规则。比如:

-- 状态字段 1:待支付 2:已支付 3:已退款 4:已关闭 status TINYINT

别小看这个“值域枚举”,它对模型的帮助比想象的更大。用户说“查一下已支付的订单”,模型本来要猜status=2还是status=3,现在直接抄答案就行了。

第二是同义词表。整理一份“业务术语→字段”的映射表,比如“客户=user/customer/member”、“金额=amount/total/money”。这个表既可以用来做动态选择时的关键词扩展,也可以直接写进prompt里,让模型在遇到看不懂的字段名时有个参照。

做语义增强的时候要注意一个度:注释不是越长越好。啰嗦的注释同样会产生噪声,占用token、干扰判断。我在实践中把字段注释控制在20~40个汉字以内,值域枚举占大头,含义描述只保留跟查询相关的那一句。

2.4 层级组织:从平铺列表到结构化视图

原始schema通常是一张扁平的目录:表名、字段列表、类型、注释一字排开。这种呈现方式对模型来说,信息密度太大,主次不分。

我自己的习惯是调整schema文本的组织结构,用层级化的方式呈现。大原则是:先给全貌,再给细节,相关字段扎堆,无关信息收起来。比如每张表的呈现,按“用途描述、关键字段、次要字段、关联关系”来组织,而不是把一张表30个字段一股脑全列上去。

实际操作上,我常用的模板是分段式:

[表名] 订单主表 用途:电商订单的核心流水表,一个订单一行 关键字段: - order_id:订单唯一编号 - user_id:下单用户,关联user表 - order_amount:订单实付金额(单位:元) - status:状态,1待支付/2已支付/3已退款/4已关闭 关联:user.user_id=order.user_id;order_detail.order_id=order.order_id

次要字段(如渠道来源、终端类型、备注信息)可以收敛成一个折叠描述,只有在问题涉及这些维度时才展开。这种“主次分明、关系显式”的呈现方式,比平铺30个字段的prompt效果要好很多。从注意力机制的角度想也很容易理解:模型在解码的时候,本来就该把更多注意力放在最可能用到的字段上,你的文本结构应该顺着这个方向引导它。

2.5 列级消解:在字段层做精细过滤

前面几条思路主要解决“表”层面的噪声,列级消解则是在“字段”层面做事。

列级消解的核心动作是三层过滤。第一层:按类型过滤,一些明显不会参与查询的字段(比如内部标识、系统日志字段、大文本字段)直接不进入prompt。第二层:按使用频率过滤,基于历史查询日志统计各字段的出现频次,低频字段可以做折叠处理。第三层:按问题相关性过滤,在动态选择阶段,如果一个问题明确只关心金额和状态,那其他字段就可以用一行概述代替逐项列举。

做列级消解的时候需要特别谨慎的一点是:过滤会引入不可逆的信息丢失。你以为那个字段不会被用到,结果用户问了一句带“创建人”的查询,你恰好把创建人字段折叠掉了,整条SQL就生成不了。所以列级消解一定不能是硬编码的死规则,它应该依赖动态路由——不同的问题,落到的schema裁剪结果要能跟着变。我在生产环境有一条经验:宁可让字段列表略宽(多出20%的token),也不要因为裁得太狠导致漏字段。

3. 实操落地:从零搭建一套Schema简化流水线

理论讲完,上实操。我把自己在项目里反复打磨过的一套完整流程整理出来,从静态画像到动态schema构建,一共四步。这套流程的核心目标有量化指标:单次查询的schema token量从平均8500降到了2200左右,端到端SQL生成准确率提升了12个百分点,同时P95延迟下降明显,因为输入变短、模型推理也变快了。

3.1 第一步:构建Schema静态画像

动手简化之前,先花两天时间把schema的“底数”摸清楚。我给每张表、每个字段建了一张画像卡,包含这样几个维度:

{ "table": "order", "field_count": 42, "row_count": 8500000, "hot_level": "high", "join_count": 6, "fields": [ { "name": "order_amount", "type": "decimal(18,2)", "comment": "实付金额", "num_mentions_in_queries": 89, "num_joins": 2, "is_join_key": true, "sensitivity": "low" } ] }

这份画像卡的数据来源有三个:数据库元数据(表结构、索引、外键)、历史查询日志(字段/表的被引用频率、join使用频率)、业务知识库(字段的业务含义)。

有了画像卡之后,静态裁剪就变得有依据了:热表和高频字段进入全量展示区;低频字段进入折叠区;从不出现在查询里的字段直接不进入prompt。我习惯用一个简单的评分公式来做字段优先级排序:

score = 0.4 × 查询引用频率归一值 + 0.3 × join参与度归一值 + 0.2 × 业务关键程度(人工标注) + 0.1 × 类型兼容度

评分高于阈值的字段进入全量展示,低于阈值但仍有业务存在感的进入折叠描述,接近零分的直接进黑名单。这个公式不复杂,但能有效避免“一刀切”的误伤。

3.2 第二步:三级简化的具体动作

有了画像,下面做三级简化:库级、表级、列级。

库级简化其实就是业务域拆分,前面已经讲过,不再赘述。表级简化做的事情比较细:

  • 对每张表做join拓扑分析,画出表之间的关联网络,识别“桥梁表”——那些自身可能不常被直接查询、但承担了大量关联关系的表。这类表要单独标记,动态选择的时候优先保留。
  • 对分表(如按月份拆分的订单表)做合并处理。在schema层面,把order_202401到order_202412合并成一张“逻辑表”order,只在关联信息里注明物理分表规则。这样模型看到的对象数大幅减少,而生成SQL时可以基于逻辑表名做日期路由,再由下游改写器做物理分表映射。
  • 对孤儿表(没有任何关联关系、且历史查询从未引用过的表)直接移出schema字典,不进候选集。

列级简化则要围绕“字段职责”做。我给字段分了个类:去重键类(id、uuid类)、业务属性类(status、amount类)、时间维度类(created_at类)、技术字段类(内部版本号、埋点字段类)。针对不同类别采用不同策略:去重键类字段保留但压缩描述;技术字段类直接裁剪;时间维度类在问题涉及时间过滤时动态展开,不默认全量展示。

这里有一个很重要的操作细节:简化过程中要保留一份“完整schema”作为兜底。动态选择万一漏了表,或者模型生成的SQL引用了被裁字段,系统需要能自动回溯到完整schema重新生成。我搭的是一个两级请求机制:第一级用简化schema请求,如果生成的SQL出现非法引用,自动用完整schema重新走一遍流程。

3.3 第三步:Query驱动的动态Schema构建

静态简化解决通用问题,动态构建解决个性问题。这一步是整个流水线的核心模块,我把它拆成四个环节:问题解析、候选召回、排序与补全、schema文本组装。

先看问题解析。对用户那句自然语言问句,做三件事:实体抽取(识别出里面提到的业务对象,比如“订单”、“客户”)、意图分类(是查询型、统计型、还是多表关联型)、时间信息抽取(因为时间字段的选择对SQL结果影响很大)。这个环节我用的是小模型 + 规则的双保险,解析结果不稳定时宁可漏召也不要错召,后面还有补全环节兜底。

再看候选召回。召回阶段同时跑两路:一路是词典匹配,拿着问题里抽出来的业务词去查表名和字段注释的同义词表,命中就直接进候选;另一路是向量召回,把问题embedding化,和所有表的“用途描述embedding”做相似度计算,取TopK。词典匹配精确但覆盖窄,向量召回泛化但可能不准,两个结果做并集再排序,能兼顾精度和召回率。

排序阶段给每个候选表算一个分,考虑因素包括:文本相似度、历史查询命中频率、表的业务热度、与已选表的关联紧密度。排序不是终点,之后必须做关联链路补全。具体说,就是把候选TopK表两两之间的join关系拉出来,以图的形式找连通路径,路径上的中间表即使不在TopK里也要加回来。这一步我吃了很多亏,刚开始不做补全,动不动就因为少一张关联表导致SQL里出现孤立的表引用。

最后是schema文本组装。这一步把动态选出来的表集合拼成最终喂给模型的schema块,格式上要遵循前面讲的层级组织和列级消解原则。

3.4 第四步:Schema文本模板与Token估算

拼模板这事,我迭代了好几版,最终稳定在一个结构上。你可以直接参考:

【数据库说明】 当前数据库包含以下业务表,请根据用户问题选择相关表生成SQL。 【表结构】 [表名] 订单主表 - 用途:电商订单核心流水,一个订单一行 - 关键字段: - order_id(long):订单唯一ID - user_id(long):用户ID,关联用户表 - order_amount(decimal):实付金额,单位元 - status(int):订单状态,1待支付 2已支付 3已取消 4已退款 - 关联关系:order.user_id = user.user_id;order.id = order_detail.order_id [表名] 用户表 - 用途:注册用户信息主表 - 关键字段: - user_id(long):用户唯一ID - register_time(datetime):注册时间 - user_level(int):会员等级,1普通 2黄金 3铂金 - 关联关系:user.user_id = order.user_id 【注意事项】 - 金额字段单位统一为元 - 涉及时间统计时优先使用订单表的create_time - 用户查询历史订单时,需通过order_detail子表关联

这个模板我验证下来的两个关键点:第一,用途描述不能省,它是模型理解表语义的最短路径;第二,关联关系要显式写在表里,不能指望模型自己推理foreign key。有一段时间我图省事,把关联关系单独放在一个section里,效果明显不如现在这种“跟着表走”的写法——模型在生成JOIN的时候,注意力当下正在那张表上,你顺手把关系放在旁边,它直接就能引用。

关于token成本,也给你一个参考计算。假设动态选定3张表,每张表精简化后约30个字段上限,字段描述平均15个字,每张表描述控制在400 token左右,外加整体说明和注意事项,一次查询的schema token约为1500~2500。这远比全量schema动辄8000+的token消耗要低。Token降下来,除了省钱,更重要的是模型的推理准确率会随之上升,这个结论在我自己的多组对比实验中都很稳定。

3.5 配套工程:离线评测与回归机制

简化方案不是一劳永逸的,所以我在流水线之外搭了一套离线评测和回归机制,专门防止“简化方案上了线,效果反而变差”这类事情。

评测数据集我维护了两类。一类是标准问答对:每个问题包含原始问句、期望SQL、期望依赖的表和字段列表。另一类是schema扰动集:把线上的schema做随机变动(改名、删注释、加无关字段),用来检验系统的鲁棒性。每次改动简化策略,就跑一遍全量评测,对比前后指标。我重点关注三个指标:

  • Schema命中率:期望依赖的表和字段,系统最终是否全部出现在prompt里。低于95%就要查召回链路。
  • SQL准确率:端到端比对生成SQL和期望SQL的执行结果是否一致,采用执行级比对而不是字符串匹配。
  • Token压缩率:简化后的schema token相对全量的比例。这个指标不能单独看,因为过度压缩一定会伤害其他两个指标。

我把这三项指标的打分做成一个综合分,作为每次迭代的决策依据。

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

这部分是我从大量踩坑中捞出来的经验,按问题场景整理成速查格式,希望能帮你省掉一些试错时间。

4.1 简化过头:关键信息被裁掉,SQL生成失败

这是最容易犯的错误。表现很典型:模型生成的SQL里出现了schema中不存在的字段名,或者表引用找不到,整个生成结果作废。

排查思路分三步。第一步,看动态选择阶段是否漏召了表。翻日志,看用户问题对应的候选表TopK里有没有目标表。如果没被召回,问题出在召回链路:要么同义词表没覆盖,要么向量相似度阈值太高。第二步,看列级消解是否裁掉了关键字段。有些字段在历史查询里低频,但恰好被某类问题依赖,这种只能靠人工抽检补回来。第三步,看关联补全是否失效。检查join链路补全代码,看看是不是候选表的连接路径计算有bug。

我的最终防线是上面提过的“完整schema兜底机制”:一旦检测到SQL引用非法字段,自动用全量schema重新推理一次,并对这种case做日志标记,每周复盘一次,把反复触发兜底的字段或表重新加回简化列表。

4.2 注释和同义词表维护跟不上

schema简化效果高度依赖注释质量和同义词覆盖度,但这两样东西天然会“过期”。业务上新表、字段改名、注释口径变化,都是常态。

我的经验是:注释补全不能只靠人工,也不能只靠模型,要把两者结合成一个定期执行的流程。每月一次自动扫描:从DDL变更记录里找出新增表/字段,用规则+大模型批量生成注释草稿,再按字段热度排序做人工抽检。抽样比例不用高,热度Top10%的字段全检,其余随机抽20%即可,因为那些几乎用不到的字段注释出错,对查询准确率影响微乎其微。

同义词表也有同样的过期问题。我在同义词表里加了一个“置信度”字段,词条经过实际查询验证(即该同义词确实帮助模型生成过正确SQL)才把置信度置为高,没验证过的词条只做低置信度匹配,避免乱扩展反而引入噪声。

4.3 动态选择结果不稳定:同一类问题,召回的表不一样

这个问题很隐蔽,但影响很大。用户问“查一下昨天订单”,有时候召回3张表,有时候召回5张表;有时候带了order_detail,有时候没带。召回结果不稳定,生成SQL的准确率自然也不稳定。

原因通常有两个。一个是向量召回阶段的不确定性:用户问法措辞稍微变一下,embedding结果就有波动,TopK自然跟着变。另一个是排序阶段没有加“历史偏好”约束:某些组合的表对在历史查询中已经被证明是高频率一起出现,应该在排序时加权。

解决办法是给候选召回加确定性约束:如果词典匹配阶段已经命中了某些表,这些表是“必选表”,不许被后面的排序挤掉;同时维护一张“表共现矩阵”,记录历史查询中表对共同出现的频率,排序阶段把共现频率作为一项加权因子,这样同类问题每次召回的候选集会收敛到一个更稳定的结果上。

4.4 评测集指标好,线上效果崩

这是最棘手的场景之一。离线评测时SQL准确率很高,上线之后用户随便问几个问题就露馅。

我复盘过几次,本质原因基本都是:离线评测集和线上真实查询的分布差异太大。评测集里的问题大多挑的是“好问题”——表名明确、语义清晰;线上用户的问法五花八门,口语化严重、指代不明、隐含条件多。如果你用跟评测集一样处理方式去应对线上问题,召回和解析全都会失真,简化策略的效果自然体现不出来。

应对之策是做线上场景的评估集补充。把线上日志里真实用户问句脱敏后收集一批,每半个月抽100条做人工标注,补充进评测集。同时做线上A/B:比较“开简化”和“全量schema”两条链路的生成SQL准确率,而不是只看简化链路自身的命中率。全量schema链路效果差,但它是你判断简化链路有没有帮倒忙的基准线。

还有一个容易忽略的细节:线上问句往往带着前后文语境,比如上一轮说了“本月”,下一轮说“那上个月呢”。如果简化链路不做指代消解就直接走召回,很可能漏掉时间相关的表。我在流水线入口做了一层轻量的历史上下文压缩,把前几轮对话中的关键实体提取并合并进当前问题,再进动态选择模块,效果立竿见影。

4.5 Token降下来了,但生成SQL质量没有明显提升

有的朋友做简化,只看token压缩率,觉得从8000降到2000就成功了。其实耗token的下降和效果提升之间不是必然因果关系。

我见过一种情况:简化做得很极致,Token确实压得很低,但模型能看到的“线索”也被砍光了——表之间的关系没了、字段的值域说明没了、同义词提示没了。模型拿着一个骨感得像骨架一样的schema,连蒙带猜,准确率不升反降。

这里有一个平衡点。我自己的经验是:简化的目标是让schema里的每个token都有明确的“用途指向”,而不是单纯追求少。同样2000 token,一份是只列表名字段名的“极简目录”,另一份是有注释、有值域枚举、有关联关系的“精读材料”,效果天差地别。每次评估简化效果,不要只盯着token,要对比同一条问题在简化前后的SQL正确率变化。

4.6 冷启动:没有历史查询日志,动态选择无米下锅

很多新项目启动时会面临:库里全量schema摆在那,但历史查询日志几乎是空的,没法统计字段热度,也没法建共现矩阵。动态选择模块等于没有训练数据。

冷启动阶段,我建议用两条腿走路。第一,不做激进的列级过滤,只做库级裁剪和表级裁剪,宽进严出,宁可让模型多看两张表,也别让它找不到表。第二,利用业务文档和DDL里的索引信息来做初始热度估计:有复合索引的字段,大概率是查询条件;外键字段,基本都会参与JOIN。把这些先验信息转化成初始评分,等线上跑了两三个月再慢慢切换到真实统计驱动的画像。

冷启动阶段还有一个好处是试错成本低:没有存量用户,简化策略可以激进一些去测试效果,但一定记得保留全量schema兜底开关,随时能回退。

最后分享一点个人体会

做了这么多轮schema降噪的实验和落地,我最大的体感变化是:注意力从调模型转到了调输入。模型那边几乎没怎么动,反而是每天跟DDL、注释、同义词表、召回逻辑打交道,效果曲线却一路上扬。这件事也让我重新理解了NL2SQL的边界——模型的推理能力是底座,但决定最终效果高度的,往往是你喂给它“看什么”的系统设计。

所以如果你现在遇到NL2SQL效果上不去的瓶颈,不妨先别急着换更大的模型。把库里的schema拉出来,做一轮画像、剪一批无关表、补一排注释、建一条动态选择链路,大概率比换模型带来的收益更直接、更便宜。往后如果再扩展,可以把schema简化结果做成版本化的配置,每次业务变更自动触发重新评估,形成一个持续迭代的正循环。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进