不敢。Text2SQL 生成的 SQL 不该直接执行,工程底线是把执行权从模型手里拿走,交给一层确定性的安全引擎。模型负责把自然语言变成结构化查询意图,引擎负责让这条意图安全落地------两层职责分开,缺一层都是把生产库暴露给概率性输出。
这套三层闸是向量空间JBoltAI框架里 Agent 智能问数的工程实现,下文按闸门拆开讲,文末给一份可以带走自查的清单。
风险面:模型输出不能当 SQL 信任
风险来自三个方向。模型幻觉,表名列名编造或张冠李戴;提示注入,用户在问题里夹带指令诱导模型改写语义;越权查询,模型"热心"地查了用户根本没权限看的表。
根子上是一对语义错配:LLM 的输出是概率性的,SQL 执行是确定性的。同一个问题换个问法,模型可能生成另一条语句;而数据库对每条语句的处理是严格确定的。概率性的东西直接进确定性环境,出事只是时间问题。
所以解法不是"把模型调得更稳"------提示词写得再好也只是降低概率------而是在模型和数据库之间加一层确定性的守门人。守门人自己不理解自然语言,它只认结构化规则,规则之内放行,规则之外拒绝。
入口粗筛:危险关键字与表名格式
头一道闸做粗筛。危险关键字黑名单拦截 DROP、DELETE、UPDATE、TRUNCATE、ALTER 这类改写性语句------问数场景只该有查询;表名格式校验挡住明显不合规范的引用,比如编号拼出来的野表名。黑名单的维护有个原则:跟语句类型走。问数场景天然只读,白名单放行 SELECT,比追着黑名单补更稳。
这层的价值在成本和误伤的平衡:规则简单、执行快、几乎不误伤正常查询,先把九成明显问题拦在门外。但它挡不住精心构造的注入,也拦不住"语法正确、权限越界"的查询------所以只能当粗筛,不能当防线。
机制层:占位符参数化,让注入不可达
第二道闸解决注入。动态值全部转成占位符,由 JDBC PreparedStatement 做参数绑定:
sql
-- 模型原始输出:动态值直接拼在语句里
SELECT order_amt FROM t_order WHERE cust_id = 'CUST_88'
-- 引擎改写:动态值出语句,进绑定参数
SELECT order_amt FROM t_order WHERE cust_id = ?
-- setString(1, "CUST_88") 之后才送数据库执行
区别在语义层面:拼接出来的 SQL 里,值有机会被当成代码执行;参数绑定之后,值永远只当值处理,注入这条路在机制上就不存在了。不是"过滤得足够干净",是根本没有执行通道。
实现上有个细节值得注意:改写要发生在引擎内部,模型侧拿到的仍是完整语句用于校验回显,真正下发数据库的是绑定后的形态。两条路径不同,才谈得上隔离。凡是在应用层拿模型输出直接拼 JDBC 字符串的写法,这层闸等于没装。
权限层:语法树校验,只碰授权的表和列
第三道闸管越权。引擎用 JSqlParser 把 SQL 解析成语法树,逐节点校验表名和列名是否在当前用户显式授权的清单里。模型引用了授权之外的表或列,整条语句直接拒绝执行。
校验的判定流程可以压缩成三步:
text
解析 → 得到语句引用的全部表名与列名
比对 → 逐项对照当前用户的授权清单,表级与列级分别核对
裁决 → 任一引用越界,整条语句拒绝,不出数据库连接池
关键在"语法树层"四个字。字符串层的过滤可以被大小写、注释、编码变体绕开;语法树是解析器结构化的产物,绕不开。模型想查 salary 列而授权清单里没有,那这条查询就到不了数据库。
最小权限到这里才真正贯穿:接口层管人能不能用问数,语法树层管这条 SQL 能碰哪些表哪些列,权限判断跟着语句走,不跟着会话走。
方言层:六种数据库一套引擎
Text2SQL 要落地,绕不开国产库。引擎的方言层覆盖六种外部数据库,老系统只读接入、不改一行代码;达梦经过 22 个自然语言问数用例、3 轮真实生成验证全部通过。
国产库的坑很具体:保留字冲突会让一条在 MySQL 里正常的语句直接报错,比如列名撞上数据库关键字,同一条 SQL 两边表现完全不同。这类问题收进方言层统一处理,业务侧不感知。方言层不是翻译器,是同一套安全语义在不同数据库上的等价实现------三层闸门在每种库里都成立。自研引擎和绑定执行这条路,恰好也摆脱了对特定厂商 SDK 的依赖------协议适配做在引擎里,换库不动上层。
图查询走同样的思路。Cypher 侧做只读校验,加 graphId 强制隔离,不同租户的数据在语句层面就互相看不见。SQL 和 Cypher 两条链路共用同一个设计原则:模型可以生成,执行必须过闸。
剩下的边界:引擎管不住的事
这份能力有边界。它管得住"生成的 SQL 安不安全",管不住"这个问题本身该不该问"------问数权限的分级、敏感字段的屏蔽,是账号权限和治理层的事,得在语句生成之前就定义好。方言覆盖之外的数据库,接入前要先过一轮用例验证,不能默认可用。
给一份自查清单,审自己的 Text2SQL 链路时逐条对:
| 检查项 | 判断标准 |
|---|---|
| 执行权归属 | SQL 是否经独立引擎下发,模型是否接触数据库连接 |
| 动态值处理 | 是否全部占位符参数绑定,有无任何字符串拼接路径 |
| 越权防护 | 是否在语法树层校验表列授权,而非字符串过滤 |
| 语句类型 | 是否只读,改写性语句有无独立审批通道 |
| 方言验证 | 目标库是否过真实用例回归,保留字冲突有无处理 |
| 图查询隔离 | 只读校验与租户隔离是否同样成立 |
结论与要点回顾
Text2SQL 的安全不是模型问题,是工程问题。三层闸各司其职:关键字粗筛拦明显问题,占位符参数化让注入机制上不可达,语法树校验把最小权限贯穿到列。模型只负责理解问题,执行权从头到尾在引擎手里。
判断一条 AI 问数链路能不能上生产,就看这六行自查清单能不能全部打勾。基于 JBoltAI 框架构建的 JBoltAI本体语义平台与 JBoltAI数字员工平台,问数和审数能力跑在同一套安全引擎上,这也是它敢开放给部门负责人的前提。