选SQL解析器这件事,你可能已经踩过坑。从GitHub上把Star最多的那个go get下来,跑个简单的SELECT测试一切正常。可一旦业务里用到了CTE(公共表表达式),它要么直接panic,要么返回一个残缺的抽象语法树(AST),导致下游逻辑全部跑飞。
换个号称“更完整”的解析器?结果要么是cgo编译不过,要么为了引入TiDB的某个解析器,导致go.sum文件瞬间多了两千行依赖。
选个解析器,怎么就变得这么困难?
上个月Reddit上一篇挺火的帖子,作者自己写了个解析器,号称比pg_query_go快6倍。评论区争论很激烈:一方认为“快就是正义”,生产环境一天要解析几百万条SQL,性能是首要考虑;另一方则指出,那个“快6倍”的解析器连子查询的位置信息都丢掉了,根本没法用来做严谨的SQL审核。
那么,谁说的对呢?为此我专门做了一次测试,而答案可能比双方想的都要简单。
先说结论
我测试了三个主流的纯Go解析器,在Apple M1芯片上运行标准benchmark,结果如下:
| Parser |
简单查询耗时 |
内存占用 |
| xwb1989/sqlparser |
6,200 ns |
20 KB |
| vitess-sqlparser |
3,500 ns |
12 KB |
| bytebase/omni/pg |
1,350 ns |
1.3 KB |
结论是:目前AST最完整的那个解析器,性能反而最好,内存占用也最低。
“想要速度快就得牺牲功能完整性”这个直觉,被实际数据打脸了。
为什么会这样?
传统观念源于yacc/lex时代:AST节点越多,解析器就越重、越慢。
但bytebase/omni/pg采用的是手写递归下降算法。它没有yacc生成的状态机开销,AST节点直接映射到函数调用,跳过了中间层。
这说明了:架构选对了,功能完整性和高性能并不矛盾。
这对我们的启示是:不要再简单地用“功能全所以肯定慢”作为淘汰选项的理由。实际跑个benchmark,结果可能会推翻你的假设。
Star数是个陷阱
这里先不讨论刷星的问题。xwb1989/sqlparser在搜索结果里排名第一,拥有超过3k的Star。但它最后一次更新是在2018年。它不仅不支持CTE、缺乏位置信息,其内存占用也比同源的vitess-sqlparser多了一倍。
源自同一个Vitess项目的代码,经过跟进新版优化后,性能(ns/op)直接减半。
Star数更多反映的是历史影响力,而非项目当前的技术状态和活跃度。
你的go.mod文件不关心Star数,它只关心最后一次有效的commit。
pingcap/parser 的坑,我踩过了
它曾经是Go生态中最强大的MySQL解析器。但现在其独立版本(例如v3.1.2)在Go 1.25环境下可能直接编译失败——原因是protobuf或grpc的版本冲突。
如果你想使用它,可能需要引入整个TiDB的依赖树。
如果你正对着go get github.com/pingcap/parser这条命令产生的编译错误一脸茫然,那么这就是根本原因。
AST能力对比表,比性能数字更关键
性能差个几倍,在实际应用中可能感知不强。但核心功能的缺失,会让你在项目中期陷入僵局。下表对比了几个关键能力:
| 核心需求 |
xwb1989 |
vitess |
bytebase |
| 报错能定位到具体行号吗? |
不能 |
不能 |
能 |
| 修改AST后能无损还原为SQL吗? |
勉强 |
勉强 |
能 |
| 能解析CTE吗? |
不能 |
不能 |
能 |
| 能解析窗口函数吗? |
部分 |
部分 |
能 |
如果解析器无法处理CTE、没有位置信息,你会痛苦地发现:它的问题不是慢,而是根本做不了你需要它做的事。
“以后再说”的代价,远超你的想象
很多人的选型思路是:先找一个能简单解析的,把核心功能做起来,等以后有需要了再换。
但这个“以后”往往来得非常快。
- 产品要求SQL审核功能必须标出问题所在的行和列?但你的解析器AST没有位置信息。结论:换解析器。
- 数据血缘分析上线后,发现所有CTE查询的血缘图都缺失了一半?因为解析器跳过了WITH子句。结论:换解析器。
- 开发SQL格式化工具,修改AST后却发现所有注释都丢了?因为无法实现AST到SQL的无损往返(round-trip)。结论:换解析器。
每一次更换解析器,其带来的代码重构和测试成本,都远超最初花十分钟仔细对比AST功能表的代价。
Go生态的现状
在Java生态中,有JSqlParser和Apache Calcite这样的成熟方案;Rust生态也有sqlparser-rs作为事实标准。反观Go语言,情况有些尴尬:MySQL方向曾经最知名的解析器不再独立更新,功能最强的那个被合并进TiDB而难以单独使用。PostgreSQL方向,一个方案依赖cgo,另一个仍在快速成长。至于跨方言的通用解析方案——目前几乎没有。
Go生态欠缺的或许不是解析器,而是一个“不用多想,选它准没错”的社区共识。
选型建议
根据不同的场景,你可以这样选择:
-
SQL防火墙、日志归集、每日数百万次解析?
选择 vitess-sqlparser。它是纯Go实现,性能比同类老牌库快一倍,对于这类场景功能足够。
-
SQL审核、数据血缘分析、IDE插件、AI辅助编程工具链?
如果你的目标是PostgreSQL,那么 bytebase/omni/pg 是目前的最佳选择:性能最快、内存占用最低、AST最完整。如果是MySQL方向,目前确实没有一个完美的答案,需要根据实际情况权衡。
-
对“与数据库原生行为100%一致”有刚性需求?
可以考虑 pg_query_go(基于PostgreSQL官方解析器),但前提是你能接受其对cgo的依赖。
最后
“选快的,还是选功能全的?”——这个问题本身可能就错了。它预设了速度与功能是对立的。而今天的基准测试表明:选对架构,它们完全可以兼得。
真正应该问的问题是:你需要的是一个仅仅能过滤SQL的解析器,还是一个能够理解SQL的解析器?
未来的趋势已经很清楚:需要深度理解SQL语义的应用场景(如智能审核、高级优化、AI集成),其增长速度将远超过简单的SQL过滤场景。在Go语言中进行数据库相关开发时,选择一个具备深度理解能力的解析器,可能才是面向未来的决策。如果你想了解更多开发者实战经验或技术选型讨论,欢迎来云栈社区交流。