文章摘要
作者借助多组并行Claude Code会话重写SQL解析器,本地测试解析速度提升约70倍,生产环境平均提升454倍。介绍了SQL解析器需求、最初ANTLR方案瓶颈,阐述AI辅助重写实践、测试与修复方法、迭代开发流程,还展望未来AI或成开发主流。

在通过代理优化autoresearch提升查询性能后,我希望挑战一项更具复杂度的工作。最终我借助多组长期运行的并行Claude Code会话,完成了一款SQL解析器的重写工作。最终产出包含1.6万行手工编写的解析器核心代码、5000行配套工具代码,以及数千行测试用例代码,本地基准测试显示其解析速度提升了约70倍。

在所有真实业务场景的查询中,新解析器的行为与旧版本完全一致,仅在极少数极端边缘案例中存在细微差异——比如针对类似`SELECT SELECT FROM FROM WHERE WHERE AND AND`这类合法但极具迷惑性的SQL语句的测试场景。以下是我实现这一成果的完整过程,以及从中获得的经验总结。

为何需要SQL解析器?

我们的数据分析平台支持用户直接通过SQL访问数据,为了实现这一能力,需要将用户提交的SQL语句转换为底层ClickHouse可执行的原生SQL。这一设计背后有三个核心考量:

  • 我们希望为用户提供与数据库物理存储结构无关的逻辑数据视图
  • 这一抽象层允许我们在不影响现有查询的前提下,对数据库底层架构进行迭代优化
  • 我们可以依托这一层级实现各类性能优化策略与细粒度的访问控制机制

平台内的各类工具,包括产品分析、会话回放、错误跟踪等模块,都通过统一的SQL转译流程处理查询。在完成转译前,我们首先需要通过解析器将用户输入的SQL转换为抽象语法树(AST),这也是整个流程的核心入口,直接对接不受信任的用户输入,下游的所有访问控制、性能优化都基于这棵语法树展开。

最初的ANTLR实现方案

在AI编码工具普及之前,手工编写并维护一款合格的SQL解析器是一项耗时极长的工作,即便它能显著优化响应延迟,投入产出比也往往不尽如人意。因此我们最初选择了ANTLR——一款成熟的开源解析器生成工具,开发者只需以声明式语法在.g4文件中定义规则,即可自动生成大部分解析器代码。

我们采用的C++版本ANTLR本身运行效率不错,但它的运行机制存在天生的性能瓶颈:ANTLR会将语法规则编译为带栈的非确定性有限自动机(ATN),通过通用解释器遍历图结构完成解析,没有手工编写的递归下降解析器那样直接的执行路径,且为了处理多候选路径需要动态前瞻模拟,整体性能无法与手工优化的解析器相比。

AI辅助解析器重写实践

随着AI辅助开发工具的成熟,手工编写高性能解析器变得更具可行性。但直接让AI生成完整的解析器并不现实,我同时尝试了两种开发路径:

  1. 以极致性能为目标,采用递归下降结合Pratt表达式循环的架构,仅在必要时添加前瞻与回溯逻辑
  2. 优先确保实现的稳定性,尽可能贴合原有ANTLR解析器的行为,但将状态转换通过显式代码实现,而非依赖通用的图遍历机制

最终两种方案的表现相当,不过我在投入数天工作后才意识到这一点。我的核心目标是确保新解析器与原有基于C++的ANTLR解析器行为完全一致,因此将原有解析器作为参考基准,通过测试驱动开发的方式不断修正差异。

系统化的测试与差异修复

最初的测试用例来自已有的回归测试套件,但当这些用例全部通过后,我需要寻找更多的差异场景。我使用了基于属性的测试库Hypothesis,通过定义“新解析器与基准解析器行为一致”这一属性,让工具自动生成不符合该属性的SQL语句作为测试用例。

为了让Hypothesis生成符合SQL语法的测试数据,我与AI协作开发了一款基于ANTLR语法文件自动生成SQL语句的工具,后续还添加了Token交换、括号调整等增强生成逻辑的步骤。值得一提的是,编写SQL生成器的过程本身,也需要为.g4语法文件编写解析逻辑,这形成了一个有趣的闭环。

虽然基于属性的测试能高效生成测试用例,但AI生成的修复方案往往存在脆弱性,比如仅针对单个问题添加单Token前瞻,却无法适配更通用的场景,且随着上下文窗口限制,AI容易遗忘语法规则和基准实现的细节。解决这一问题的关键在于提示工程:在生成修复代码前,先将语法文件和基准解析器的C++源代码加载到AI的上下文中,这一调整大幅提升了修复的质量和通用性。

除了基于属性的测试,我还通过多种方式生成测试用例,包括从生产环境的匿名查询日志中提取真实SQL,以及让AI主动思考边缘场景来构造测试数据。我还开发了工具让测试生成流程在后台持续运行,将失败的测试用例自动保存,方便AI随时调用修复。对于无法通过现有工具精简的测试用例,我使用了专用工具来简化到最小重现步骤。

此外我还添加了基于代码覆盖率的测试生成逻辑,让生成的SQL能够覆盖更多的语法结构,帮助发现隐藏的边界场景。这一优化虽然不是实现生产级准确率的必要条件,但确实帮助我找到了不少之前遗漏的细微问题。

完整迭代开发流程

最终我形成了一套完整的迭代开发流程:

  • 从测试生成工具生成的用例、真实生产查询、回归测试和AI构造的边缘场景中获取新的失败测试
  • 将精简后的失败用例加入回归测试套件
  • 针对每个问题思考通用的修复方案,结合语法规则和基准代码确认修复逻辑
  • 实施修复并生成人工可读的总结
  • 运行全量回归测试确保所有用例通过
  • 自动重新启动整个循环

由于新解析器的性能优势明显,我可以在生产环境中以影子模式运行它:同时使用原有解析器处理流量,新解析器仅做解析对比不影响最终请求,以此验证其在真实生产场景中的一致性。最初我仅测试了约5万个生产查询,而影子模式让我能够快速验证数百万次解析操作,且未发现任何偏差。原本计划运行数天,但仅几小时后我就将生产流量切换到了新解析器,同时开启了小比例的反向监控模式以持续验证一致性。

性能成果与行业展望

最终的新解析器生成的抽象语法树和源代码位置信息与原有ANTLR解析器完全一致,生产环境的基准测试显示其平均解析速度提升了454倍。这一结果远超本地笔记本上的70倍基准,因为生产环境中我们需要解析的大多是未命中缓存的长SQL语句,更能体现出手工优化解析器的性能优势。

虽然我没有亲手编写一行核心代码,但这并非“凭感觉”完成的工作:基于语法的测试生成、覆盖率引导的测试用例优化,结合严谨的测试驱动流程,让这套开发方式达到了当前解析器模糊测试领域的先进水平。

这一实践也引发了对传统解析器生成工具的思考:未来基于AI的开发方式或许会成为主流,解析器生成器作为基准参考,结合大语言模型与自动化测试,能够快速构建出性能更优的手工解析器。

最终我完成的这款解析器,本质上是一款基于预测性递归下降的手写解析器,搭载了Pratt表达式核心、LL(2)前瞻游标,针对少数决策场景保留了局部试探回溯能力,全部代码由Claude Opus 4.7生成,使用Rust语言开发,于2026年5月完成。

以上内容不代表本平台立场,仅供读者参考