不能用正则直接提取表名,因正则无法处理子查询、CTE、引号包裹、换行注释等复杂SQL结构;应使用xwb1989/sqlparser解析AST,递归遍历AliasedTableExpr和Subquery等节点准确提取。

为什么不能用正则直接提取表名
正则匹配 FROM 或 JOIN 后面的标识符,看似简单,实际会漏掉大量合法 SQL 结构:子查询里的表、CTE(WITH 子句)、括号嵌套别名、反引号或双引号包裹的表名、跨行换行、注释干扰。更麻烦的是,SELECT * FROM users u JOIN (SELECT id FROM logs) l ON u.id = l.id 这类语句里,logs 根本不跟在 FROM 后面,但它是真实参与查询的表。
推荐用 sqlparser 解析 AST
Go 生态中稳定可用的是 xwb1989/sqlparser(原 GitHub 上的 vitess 分支),它能将 SQL 转成结构化 AST,再递归遍历即可准确提取所有表引用节点。
安装:
go get github.com/xwb1989/sqlparser
关键逻辑是识别 *sqlparser.AliasedTableExpr 类型节点,它的 Expr 字段可能是:*sqlparser.TableName(最常见)、*sqlparser.Subquery(需继续解析)、*sqlparser.Values(可忽略)。
立即学习“go语言免费学习笔记(深入)”;
示例片段:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
stmt, err := sqlparser.Parse(sql)
if err != nil {
return nil
}
var tables []string
sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) {
if t, ok := node.(*sqlparser.AliasedTableExpr); ok {
if tn, ok := t.Expr.(*sqlparser.TableName); ok {
tables = append(tables, tn.Name.String())
}
}
return true, nil
}, stmt)
注意 CTE 和子查询里的表不会自动展开
WITH user_logs AS (SELECT * FROM logs) 中的 logs 不会在顶层 FROM 出现,必须显式遍历 *sqlparser.With 节点;同理,SELECT * FROM (SELECT id FROM events) e 的 events 在子查询 AST 内部。
实操建议:
- 先检查
stmt是否为*sqlparser.SelectStmt,再取其With字段遍历 CTE - 对每个
From里的*sqlparser.AliasedTableExpr,若Expr是*sqlparser.Subquery,需递归调用相同提取逻辑 -
TableName的Name是sqlparser.IdentifierCI,调用.String()才得真实表名,不要直接用.Name字段
兼容性与性能边界要心里有数
xwb1989/sqlparser 支持 MySQL 语法为主,对 PostgreSQL 的 LATERAL JOIN、SQL Server 的 PIVOT 等不支持,解析会失败并返回错误。如果业务 SQL 来源混杂,需加 recover 或预检 sqlparser.IsSelect。
单条语句解析耗时在微秒级,但频繁调用(如每请求一次)要注意复用 sqlparser.Parse 的内部缓存——它本身无全局状态,但字符串解析涉及大量内存分配,建议在高并发场景下做简单池化或限流。
真正容易被忽略的是:视图定义中的表名、存储过程中嵌套的动态拼接 SQL,这些根本不在当前语句 AST 里,解析器无能为力。需要配合数据库元信息查 INFORMATION_SCHEMA.VIEWS 或静态代码扫描才可能补全。

















