1. Text2SQL 为什么需要 SQL 检查
Text2SQL 的目标,是把自然语言转换成 SQL,再从数据库获取结果。例如用户询问“统计今年每个城市的订单数量”,模型可能生成:
SELECT city, COUNT(*) FROM orders WHERE year = 2026 GROUP BY city
在交给数据库执行前,系统还必须确认:这是只读查询吗?访问的表是否允许访问?返回字段是否敏感?是否包含多条语句、危险函数、异常嵌套或高资源消耗?这类执行前检查通常称为 SQL Guard。
SQL 检查不是 Text2SQL 模型,而是模型和数据库之间的安全、权限、资源和质量控制层。常见实现有两种:基于正则表达式的文本检查,以及基于解析器 AST 的结构化检查。两者都可以工作,选择取决于项目规模、SQL 复杂度、风险等级和维护成本。
2. AST 是什么
AST 是 Abstract Syntax Tree,即“抽象语法树”。它把 SQL 从字符串解析成有层次的结构。
SELECT city, COUNT(*) AS total
FROM orders
WHERE amount > 100
GROUP BY city
可以抽象为:
Select
├── Projection: city, COUNT(*) AS total
├── From: orders
├── Where: amount > 100
└── GroupBy: city
Guard 面对的是查询类型、表节点、字段节点、函数节点和条件节点,而不是一整段难以可靠分析的文本。
3. 方式一:基于正则表达式的 SQL 检查
优点
- 实现成本低,适合快速判断是否出现
DROP、DELETE或分号。 - 执行速度快,适合最前面的粗粒度预过滤。
- 不需要引入 SQL 解析库。
- 对非常固定、非常简单的 SQL 模板足够有效。
缺点
SQL 是有语法结构的语言,正则很难长期承担结构分析职责。
3.1 容易受大小写、空格和注释影响
SeLeCt name FROM students
如果规则只匹配大写 SELECT,就会误判;支持大小写后,还要处理换行、注释和方言差异。
3.2 难以区分代码和字符串
SELECT 'DROP TABLE students' AS example FROM students
这里的 DROP TABLE 是字符串内容,不是实际执行的语句,简单搜索关键字会产生误报。
3.3 难以准确提取表和字段
JOIN、别名、子查询和 EXISTS 会形成作用域。正则需要自己处理这些语义,容易漏表、错认别名或把字符串内容当成字段。
3.4 难以判断查询语义
SELECT * FROM orders
SELECT COUNT(*) FROM orders
两者都含有 *,但前者返回所有字段,后者只是计数。正则可以找到字符,却很难稳定判断它所在的语法节点。
3.5 规则容易膨胀
随着 CTE、窗口函数、子查询、集合运算和数据库方言增加,正则往往变成难以维护的补丁集合。
4. 方式二:基于 AST 的 SQL 检查
优点
- 能识别真实语句类型。 可以明确区分 SELECT、INSERT、UPDATE、DELETE、DDL 和多语句。
- 能提取真实访问范围。 可以遍历表、列和函数节点,再与 Text2SQL 数据目录和字段权限匹配。
- 可以编写结构化策略。 例如只允许单条 SELECT、禁止
SELECT *、禁止危险函数、只允许登记表和字段。 - 拒绝原因更容易解释。 可以返回
SQL_MULTI_STATEMENT、SQL_TABLE_NOT_PUBLISHED、SQL_WILDCARD_PROJECTION等稳定原因码,指导模型重试和管理员排查。
缺点
- 解析库不等于完整方言兼容。 一个解析器可能支持标准 SQL,却不完整支持 MySQL、PostgreSQL、ClickHouse 或 Spark SQL 的所有语法。
- AST 不理解业务含义。 它知道查询了
student_id,但不知道这个字段是否代表当前登录用户;业务权限仍需要目录、行级策略和服务端上下文。 - 复杂语法需要更多策略。 窗口函数、递归 CTE、集合运算和用户自定义函数都需要独立的权限和资源规则。
- 解析本身也要限资源。 对 SQL 长度、嵌套深度和解析耗时设置上限,避免异常输入消耗过多资源。
5. 两种方式如何选择
没有一种方式适合所有 Text2SQL 项目。可以从以下维度判断:
| 场景 | 更适合的方式 | 原因 |
|---|---|---|
| 个人项目、Demo、SQL 模板固定 | 正则表达式 | 实现快,依赖少,维护成本低 |
| 只支持简单单表 SELECT | 正则表达式 | 语法范围小,规则容易覆盖 |
| 需要识别 JOIN、子查询、聚合 | AST | 结构信息更准确 |
| 需要表级、字段级权限 | AST | 可以直接遍历表节点和列节点 |
| 支持多种数据库方言 | 视解析器支持情况决定 | AST 更结构化,但需要确认方言兼容性 |
| 高风险生产系统 | AST 或正则 + AST | 通常需要更细粒度的结构检查 |
| 已有成熟正则规则且变更很少 | 正则表达式 | 迁移收益可能不足以抵消改造成本 |
小项目不必为了使用 AST 而引入复杂依赖;复杂项目也不必因为 AST 更先进就完全重写已有规则。可以先用正则满足基础需求,再对复杂 SQL 增加 AST 检查。
6. Text2SQL 中的 SQL 检查流程
自然语言问题
↓
模型生成候选 SQL
↓
长度和字符级预过滤
↓
AST 解析
↓
检查语句类型、表、字段、函数和查询形状
↓
匹配数据目录与业务权限
↓
设置超时、最大行数和最大返回字节数
↓
参数化执行
↓
结果脱敏、审计和回答生成
可以只使用其中一种,也可以组合使用。组合时,正则负责快速处理明显异常输入,AST 负责复杂结构检查;但组合会增加依赖和维护成本,应根据实际收益决定。
7. 一个 Text2SQL SQL 检查示例
假设目录只开放 orders 表的 city、amount 和 created_at 字段。
合法查询:
SELECT city, COUNT(*) AS total
FROM orders
WHERE created_at >= ?
GROUP BY city
Guard 可以确认:它是单条 SELECT,访问已登记表,投影字段已登记,使用允许的聚合函数,时间值通过参数绑定。
应拒绝的查询包括:
SELECT * FROM orders
SELECT city FROM internal_salary
SELECT load_file('/etc/passwd')
SELECT city FROM orders; DELETE FROM orders
对应原因分别是通配符投影、未登记表、危险函数和多语句写操作。
8. SQL 检查对回答质量的帮助
AST 不会直接让模型更聪明,但会减少错误 SQL 进入数据库,并让错误更容易被模型修正:
- 降低访问错误表、错误字段的概率;
- 提前发现多语句和不支持结构;
- 用稳定原因指导模型重新生成;
- 避免
SELECT *导致敏感字段泄露或上下文过长; - 让原生 SQL 和结构化查询遵循一致的数据目录规则;
- 配合资源限制,减少大结果集造成的失败。
Guard 不能替代 Text2SQL 评测,仍需关注执行准确率、结果准确率、语义等价性、延迟和安全拒绝率。
9. 什么时候适合用正则
正则适合判断输入长度、过滤控制字符、做日志脱敏、在 AST 解析前拒绝明显异常输入,以及处理完全固定的模板和简单项目的基础 SQL 检查。
当 SQL 开始包含大量 JOIN、子查询、复杂聚合,或者需要细粒度表字段权限时,正则维护成本会明显上升,此时可以评估引入 AST。
10. 什么时候适合用 AST
AST 适合需要理解 SQL 结构的场景,例如复杂 JOIN、子查询、聚合、函数检查、表字段权限和多种拒绝原因。引入前应确认解析器对目标数据库方言的支持情况,并评估依赖、性能和规则维护成本。
11. 落地建议
- 先明确需要检查的风险和 SQL 范围。
- 小范围、低风险项目优先选择成本更低的实现。
- 复杂 SQL 或细粒度权限场景再评估 AST。
- 建立表、字段、函数和行级策略的数据目录。
- 所有过滤值使用参数绑定,禁止拼接用户输入。
- 设置解析超时、查询超时、最大行数和最大返回字节数。
- 为拒绝原因建立指标和管理员审计记录。
- 用真实业务问题覆盖大小写、注释、别名、JOIN、子查询和方言差异。
12. 总结
正则和 AST 都可以用于 Text2SQL 的 SQL 检查:正则的优势是简单、快速、低成本;AST 的优势是结构准确、可解释、可扩展。小项目可以优先考虑正则,复杂项目可以选择 AST,也可以逐步组合两者。
比较稳妥的架构是:
正则表达式或 AST
+ 数据目录与业务权限
+ 参数化执行 + 数据库只读权限 + 结果脱敏与审计
Text2SQL 的目标不是尽可能执行模型生成的 SQL,而是在项目可接受的成本和安全边界内,执行足够准确、可解释、可审计的查询。