版权与内容来源声明
本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容,均在附表 A 中标注来源;引用官方原文保持原样,不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准,标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式,也不对任何收益结果作承诺。转载请注明出处。
给一个 Text-to-SQL 服务接上 40 张表的业务库,第一个卡点往往不是提示词写得好不好,而是预算:把这 40 张表的建表语句原样拼进提示词,光表结构本身就是 3619 个 token。若把这一轮的总预算按「schema + 2000 token 答案余量」来分,它一个人占掉 64.4%——问题还没问出口,六成预算已经花在让模型「认识这个库」上。这篇要算的,就是这笔被长期忽略的账:全量 schema 到多少 token 才该动手剪,以及剪的时候依据到底是什么。
一、先分清三块开销:表结构也在按 token 计费
Text-to-SQL 一轮问答吃掉的上下文,至少是三块,不是一块:用户问题、库表结构(schema)、留给模型输出的答案余量。前两块是输入,第三块是输出。真正被漏算的,是中间那块。
1.1 三块里只有 schema 会随库规模自己长大
用户问题基本是一句话,长度稳定;答案余量可以按任务预留;只有 schema 不受你控制——表在库里怎么长,它就怎么涨。这里说的 schema,人话就是「这个库长什么样」的说明书:有几张表、每张表有哪些列、列是什么类型、有没有主键。落到 SQLite 里,它就是你建表时敲的那几条CREATE TABLE。SQLite 官方文档在 CREATE TABLE 一节里把它定义得很直白:
The “CREATE TABLE” command is used to create a new table in an SQLite database.
每一张表都对应一条这样的语句,表越多,拼起来的那段文本就越长。
1.2 它叫开销,是因为每一轮都要重新付
如果每轮问答都把全量 schema 拼一遍,这笔开销是按轮次重复计费的。多轮追问、批量报表这类场景,重复次数一上来,账就压不住了。这也是为什么它值得单独拿出来算,而不能当成一次性的建库成本摊销掉。
换个角度看:模型并不需要「每次都知道全部 40 张表长什么样」,它只需要知道「这一轮要碰的那几张长什么样」。前一种做法把成本摊在每一轮上,后一种把成本压到一次检索里。这个区别,是整个剪枝讨论的起点。
1.3 真实库的规模,比学术基准大得多
BIRD 数据集的摘要直接把差距摆出来了:
containing 12,751 pairs of text-to-SQL data and 95 databases with a total size of 33.4 GB, spanning 37 professional domains
同一篇摘要也点明了老基准的短板:
most of the prevalent benchmarks, i.e., Spider, and WikiSQL, focus on database schema with few rows of database contents leaving the gap between academic study and real-world applications.
库越大,schema 这块预算就越不可能被忽略。截至 2026-10-13,BIRD 仍是这一方向上常被引用的规模基准之一——而它强调的正是「大规模库 + 脏内容 + 额外知识」带来的新挑战。
二、全量 schema 到底多少 token:先把口径钉死再算
要判断该不该剪枝,先把「全量 schema 有多少 token」这个数算准;算不准,后面所有取舍都是拍脑袋。而算准的前提,是先把「schema 的 token」这五个字的口径定死。
2.1 口径先定死:算的就是 sqlite_master 里的建表原文
SQLite 官方《The Schema Table》文档说得很清楚:
Every SQLite database contains a single “schema table” that stores the schema for that database.
并且这个表的sql列里放的就是建表原文:
The sqlite_schema.sql column stores SQL text that describes the object. This SQL text is a CREATE TABLE, CREATE VIRTUAL TABLE, CREATE INDEX, CREATE VIEW, or CREATE TRIGGER statement that if evaluated against the database file when it is the main database of a database connection would recreate the object.
所以「schema 的 token」= 把sqlite_master里每张表的sql列取出来拼起来,再按某种编码器分词计数。取数用的查询是这一条:
SELECTname,sqlFROMsqlite_masterWHEREtype='table'ANDnameNOTLIKE'sqlite_%'ORDERBYname;type = 'table'是为了排掉索引和触发器;name NOT LIKE 'sqlite_%'是为了排掉 SQLite 自己维护的内部对象。
2.2 实测:40 张表的库,全量 schema 是 3619 个 token
🧪 实测环境:Python 3.13.12 / macOS / tiktoken 0.14.0(o200k_base 编码)
下面是实测脚本的核心段(节选,省略了字符数与预览的打印),完整输出原样贴在后面。
importsqlite3,tiktoken enc=tiktoken.get_encoding("o200k_base")con=sqlite3.connect(":memory:");cur=con.cursor()domains=["orders","users","products","logistics","finance","ops","marketing","inventory","supplier","after_sales","risk","audit"]foriinrange(40):t=f"t_{domains[i%len(domains)]}_{i:02d}"cols=", ".join(f"col_{j:02d}TEXT"forjinrange(14))cur.execute(f"CREATE TABLE{t}(id INTEGER PRIMARY KEY,{cols}, ""created_at TEXT, updated_at TEXT)")rows=cur.execute("SELECT name, sql FROM sqlite_master ""WHERE type='table' AND name NOT LIKE 'sqlite_%'").fetchall()full="\n\n".join(sqlfor_,sqlinrows)ntok=lambdas:len(enc.encode(s))print("表数量 :",len(rows))print("全量 DDL token :",ntok(full))print("单表平均 token :",f"{ntok(full)/len(rows):.1f}")print("只注入 1 张相关表:",ntok(dict(rows)["t_orders_00"]))print("占(schema+2000) :",f"{ntok(full)/(ntok(full)+2000)*100:.1f}%")真实输出(原样粘贴,未做任何修饰):
表数量 : 40 全量 DDL token : 3619 单表平均 token : 90.5 只注入 1 张相关表: 90 占(schema+2000) : 64.4%2.3 把这几个数整理成一张有口径的账
同一个实验的完整口径(含字符数与节省比例)整理如下。
| 指标 | 数值 | 口径 |
|---|---|---|
| 表数量 | 40 张 | 模拟业务库:订单 / 用户 / 商品 / 物流 / 财务 / 运营等 12 个域循环生成 |
| 全量建表语句字符数 | 10,718 | 拼接sqlite_master.sql后的字符长度 |
| 全量 schema token 数 | 3,619 | tiktoken 0.14.0,o200k_base 编码 |
| 单张表平均 token | 90.5 | 3,619 ÷ 40,按表均摊 |
| 只注入 1 张相关表 | 90 | 取t_orders_00一张表的建表语句 |
| 剪枝后节省比例 | 97.5% | 1 − 90 ÷ 3,619 |
| 全量 schema 占(schema + 2000)比例 | 64.4% | 3,619 ÷ (3,619 + 2000) |
取数日期:2026-10-13。口径说明:每张表按 1 个 INTEGER 主键 + 14 个 TEXT 列 + 2 个时间列构造;编码器为 o200k_base,换一个编码器数值会变;表里的「2000」是本文假设的答案预留余量,不是任何模型的硬上限,你的场景按实际输出长度改。
三、该不该剪枝看占比,剪枝按「问题涉及哪几张表」
判断要不要剪枝,不看库里有多少张表,看全量 schema 占掉这份总预算的比例——40 张表就吃掉 64.4%,这就是该剪的信号。
3.1 占比才是判据,表数是假的判据
表数和 token 不成正比:列数、类型名长度、约束多少都会影响单表体积。40 张宽表完全可能比 200 张窄表更贵。所以判据必须落在 token 占比上。给一个能直接执行的线:全量 schema 占比超过一半,就该上剪枝;占比在三成以下,先别折腾剪枝基建。本文这个 64.4% 属于必须剪的那一档。
3.2 剪枝的依据是「这条问题会用到哪几张表」
这一点常被写反。剪枝不是「把库里重要的表都列上」,而是「先判断这一轮问题会碰到哪几张表,只把那几张塞进去」。Spider 数据集的摘要恰好说明「选表」本来就是任务的一部分:
the task requires the model to generalize well to both new SQL queries and new database schemas
也就是说,模型本来就要面对「库里有 200 张表但只有 2 张相关」的局面。剪枝做的,是把这一步判断从模型脑子里挪到你的检索层:先用一次轻量的检索把候选表圈出来,再把候选表的建表语句拼进提示词。下面这段沿用上一段的rows(也就是(name, sql)列表),把「按问题选表」的最小逻辑跑了一遍:
🧪 实测环境:Python 3.13.12 / macOS / tiktoken 0.14.0(o200k_base 编码)
q="上个季度订单退款了多少"hit=[rforrinrowsif"orders"inr[0]]print("问题 :",q)print("命中表 :",[r[0]forrinhit])print("剪枝后 token :",sum(len(enc.encode(r[1]))forrinhit))print("全量 token :",sum(len(enc.encode(r[1]))forrinrows))问题 : 上个季度订单退款了多少 命中表 : ['t_orders_00', 't_orders_12', 't_orders_24', 't_orders_36'] 剪枝后 token : 360 全量 token : 3619真实项目里,这一步不会是关键词包含那么朴素,而是向量检索、关键词打分或让模型先做一次选表;但判据不变——选出来的是「这条问题涉及的表」,不是「库里的全部表」。本例中把 40 张压到 4 张,schema 从 3619 token 降到 360,还剩一层冗余,防的就是漏表。
3.3 三档方案各适合什么
| 方案 | 适用条件 | 注入内容 | 主要代价 |
|---|---|---|---|
| 全量注入 | 表少(经验上 10 张以内)且占比低 | 全库建表语句 | 预算被 schema 吃掉,表一多就崩 |
| 检索剪枝 | 表多、schema 占比过线 | 只注入与本轮问题相关的 top-k 表 | 有漏表风险,必须配召回兜底 |
| 固定子集(视图 / 宽表) | 库结构稳定、问题类型收敛 | 预先定义好的少量表 | 维护成本高,覆盖不到新问题 |
三档不是互相替代的关系:表少时用第一档最省事;一旦占比过线,第二档是主力;只有当问题类型高度收敛、库又几乎不变时,第三档才值得它的维护成本。
这份资料里有什么:如果你正在搭 Text-to-SQL 或报表 Agent,需要一份把「库表结构怎么进上下文」讲透的参考,配套的资料包里有 Agent 与 RAG 相关的完整梳理。放在资料包里,扫码即可获取:
四、按现象排障,先算占比再动刀
遇到 schema 挤爆上下文,先按现象定位,别一上来就删表。删错表比多喂表更贵——漏掉目标表,模型只能瞎编或拒答。
4.1 现象 → 原因 → 处置
| 现象 | 大概率原因 | 处置 |
|---|---|---|
| 问题还没问,上下文就被占满 | 全量 schema 占比过高 | 先算 token 占比,过一半就上剪枝 |
| 单表问答对,多表 join 老出错 | 剪得太狠,关联表没进上下文 | 把外键与前缀命名一起纳入选表 |
| 剪枝后效果时好时坏 | 选表仅靠关键词,召回不稳 | 换向量检索,并保留 top-k 冗余 |
| 改过列名后质量突然下滑 | 命中缓存里的旧 schema | schema 变更时让缓存失效 |
| 多轮对话越聊越贵 | 每轮都重复注入全量 schema | schema 段缓存,只注入一次 |
4.2 动手顺序:三步,走到第三步才动剪枝
先算:跑一遍全量 schema 的 token 与占比,把数摆出来,别凭感觉说「表有点多」。再验:占比过线才剪,不过线就别折腾,把力气花在列注释和命名上——列名和注释是免费的准确率,剪枝是要付工程成本的。后兜底:剪枝必须带召回冗余,并在 schema 变更时让缓存失效。
落到具体动作:top-k 先取 5 而不是 2,从 40 张表里选 5 张,token 大约 450,仍只占全量的 12% 出头,却比只取 1 张多了一层防漏表的安全垫;等评测显示召回稳定了,再把 k 往下压。这个顺序反过来做,很容易出现「剪完反而更差,却不知道是剪错还是提示词的问题」。
4.3 什么时候该停手
给两个可执行的判断。库里表在 10 张以内、全量 schema 占比低于三成时,先不做剪枝基建,收益抵不过复杂度;40 张表、占比六成以上时,先剪枝再谈提示词优化。停手条件同样明确:剪枝上线后如果没有可观测的提升,说明瓶颈不在 schema 这块,别继续在选表策略上加码。
这份资料里有什么:本文用到的建表与分词脚本、以及上表的取数口径,都归到了 Agent 数据一环的配套里。放在资料包里,扫码即可获取:
附表 A:本文引用事实与出处对照表
| 事实 | 出处 | 本文位置 |
|---|---|---|
| 每个 SQLite 库都有一张 schema 表,用于存储该库的 schema | SQLite 官方文档《The Schema Table》(sqlite.org/schematab.html,页面标注更新于 2023-06-16) | 第 2.1 节 |
sqlite_schema.sql列存放的是 CREATE TABLE 等语句的 SQL 原文 | 同上 | 第 2.1 节 |
CREATE TABLE命令用于在 SQLite 数据库中创建新表 | SQLite 官方文档《CREATE TABLE》(sqlite.org/lang_createtable.html) | 第 1.1 节 |
| 一张表最多只能有一个 PRIMARY KEY,普通表可省略,WITHOUT ROWID 表必需 | SQLite 官方文档《CREATE TABLE》第 3.5 节(同链接) | 第 2.2 节示例 |
| Spider 数据集含 10,181 个问题、5,693 条复杂 SQL、200 个多表数据库、覆盖 138 个领域,要求模型对新的 SQL 与新的库 schema 都能泛化 | 论文《Spider: A Large-Scale Human-Labeled Dataset for Complex and Cross-Domain Semantic Parsing and Text-to-SQL Task》,arXiv:1809.08887 摘要页 | 第 3.2 节(另见第 4.1 节示例场景) |
| Spider 上最好的模型在 database split 设定下只有 12.4% 的精确匹配 | 同上 | 第 3.1 节(说明「表多但相关表少」是长期难题) |
| BIRD 含 12,751 对 text-to-SQL 数据、95 个数据库、共 33.4 GB、覆盖 37 个专业领域;旧基准因库内容少与真实应用存在差距;ChatGPT 执行准确率 40.08%,人类为 92.96% | 论文《Can LLM Already Serve as A Database Interface? A BIg Bench for Large-Scale Database Grounded Text-to-SQLs》,arXiv:2305.03111 摘要页 | 第 1.3 节 |
| 40 张表的模拟库:全量 schema 3,619 token,单表平均 90.5 token,只注入 1 张相关表 90 token,全量占比 64.4%,剪枝后节省 97.5% | 本文实测(Python 3.13.12 / macOS / tiktoken 0.14.0 / o200k_base,取数日期 2026-10-13) | 第 2.2、2.3、3.2 节 |
写在最后:这篇用到的资料
写这篇文章时,把相关的官方文档和源码又翻了一遍,顺手也整理了几份配套的东西:
- 大模型学习路线图:从零基础到能自己动手做 Agent,按阶段说明每一步该学什么、哪些可以先跳过
- 《LangChain + LangGraph + MCP 智能体开发实战》视频课:7 个模块,从私有化部署、Embedding+RAG 到 MCP+Agent 全流程
- AI 大模型知识库(在线可查):Agent Skills 从入门到落地、Claude Skills 完全指南等专题,按目录浏览即可
- 640 套 AI 大模型行业报告 + 经典 PDF 书籍:看行业落地案例和别人怎么做的时候用得上
- 大模型零基础到精通教学视频:跟着敲一遍,比只读文档快得多
资料是我自己整理的,放在下面这个码上,扫码即可获取:
添加时备注「AI」,优先通过。
资料按「先路线、再动手、最后查漏」的顺序整理好了,建议先看学习路线那一份,照着它挑一条适合自己当前基础的路径再往下看。