新闻详情

新闻详情

首页 / 资讯中心 / 详情

第32章:MySQL SQL解析器与语法扩展源码实战

发布时间:2026/9/28 4:45:47来源:尧图网络
第32章:MySQL SQL解析器与语法扩展源码实战
1. 项目背景业务场景某 DBA 团队想要实现一个自定义的监控命令——SHOW SLOW DIGEST用于直接查看当前实例中执行最慢的 Top 10 SQL 摘要类似 Performance Schema 但不需要复杂的 JOIN。他们不想每次都写一长串查询 Performance Schema 的 SQL——希望一个简单的命令就能搞定。但他们很快发现——MySQL 的 SQL 解析器使用 Bison 语法文件sql/sql_yacc.yy——这个文件超过 2 万行。加一个新语法不只是加几行代码那么简单——需要修改词法分析器sql/lex.h、语法分析器sql/sql_yacc.yy、添加新的 SQL 命令类sql/sql_cmd.h、注册命令分派。任何一步出错——结果都是语法错误或 crash。痛点不了解解析器就无法理解 SQL 如何变成执行计划解析链路断层只知道mysql_parse()开始解析——但不知道词法分析tokenize→ 语法分析parse→ LEX 语法树构建的完整过程。语法错误信息不友好MySQL 的错误信息有时指向的位置和实际错误位置有偏差——不知道解析器内部逻辑就无法理解为什么。无法扩展 DSL如果需要在 SQL 中加入自定义关键字——不懂解析器就无法安全地添加。本章带你从 SQL 字符串到 LEX 语法树的完整解析链路——最终实现一个自定义的SHOW SLOW DIGEST命令。2. 项目设计【场景小胖看着 sql_yacc.yy 里两万多行的 Bison 语法——一脸崩溃】小胖“大师我就想加一个简单的命令——为什么需要改这么多文件而且这个sql_yacc.yy里面的 Bison 语法——看起来像是魔法咒语——完全看不懂。”大师“分开看就不难了。SQL 解析分三步——词法分析Lexer、语法分析Parser、语法树构建Parse Tree。词法分析把 SQL 字符串切成 token——比如SELECT * FROM t WHERE id 1变成SELECT_TOKEN STAR_TOKEN FROM_TOKEN IDENT_TOKEN WHERE_TOKEN IDENT_TOKEN EQ_TOKEN NUM_TOKEN。语法分析根据 Bison 规则把这些 token 组装成一颗语法树——最后生成 LEX 对象——交给优化器。”小白“那词法分析和语法分析的边界在哪怎么判断一个错误是词法还是语法”大师“词法错误是’这个单词我不认识’——比如你写了一个 MySQL 不认识的关键字。语法错误是’这几个单词的顺序不对’——比如SELECT * WHERE FROM t——每个单词都认识——但是顺序不符合 SQL 语法规则。MySQL 的sql/lex.h定义了所有关键字和符号的 token 映射——sql/sql_yacc.yy定义了语法规则。”技术映射词法分析 SQL 字符串 → token 流lex.h sql_lex.cc。语法分析 token 流 → 语法树sql_yacc.yy。生成 LEX 对象后交给dispatch_sql_command()分派执行。小胖“那解析器最终生成的’语法树’长什么样就是那个 LEX 结构吗”大师“对——但 LEX 不光是语法树——它是一个巨大的结构体——包含了 SQL 语句的所有语义信息。比如sql_command字段告诉你这是 SELECT 还是 INSERTquery_block是查询块SELECT 的 FROM 部分order_list是排序列limit_params是 LIMIT 参数。整个结构体有几百个字段——每个 SQL 类型只用其中的一部分。”技术映射LEX 结构体 SQL 语句的语义表示。包含sql_command命令类型、query_blockFROM 子句、unitUNION 查询块列表等核心字段。小白“那如果我想在 SQL 里加一个注释式的 HINT 来影响执行计划——也必须在 sql_yacc.yy 里加规则吗”大师“不一定——MySQL 在解析 HINT 注释/* ... */时有特殊处理。词法分析器在遇到/*时不会把它当成普通注释——而是进入一个特殊的 tokenizer 模式——把 HINT 内容解析为一系列的 hint token——然后传递给优化器。这种方式的好处是——HINT 语法不污染标准 SQL 语法——即使你写的 HINT 不合法——最多是优化器忽略它——而不会报语法错误。”3. 项目实战3.1 环境准备# 确认编译环境需要 bison 和 flexsudoapt-getinstall-ybison flex# 确认源码树中解析器源文件存在ls~/mysql-src/sql/sql_yacc.yyls~/mysql-src/sql/lex.hls~/mysql-src/sql/sql_lex.cc# 确认 SQL 命令基类ls~/mysql-src/sql/sql_cmd.h3.2 分步实现步骤一追踪一条 SQL 的解析全链路// 文件sql/sql_parse.cc// 入口函数voiddo_command(THD*thd){// 1. 从网络读取客户端发来的 SQL 字符串thd-m_parser_state.reset();// 2. 词法分析 语法分析if(thd-m_parser_state.init(thd,thd-query().str,thd-query().length)){return;// 解析错误}// 3. dispatch_command 执行解析后的 SQLdispatch_sql_command(thd,thd-m_parser_state);}// mysql_parse() → 被 dispatch_command 内部调用intmysql_parse(THD*thd,Parser_state*parser_state){// → MYSQLparse(thd) // 调用 Bison 生成的解析器// → LEX *lex thd-lex; // 获取解析结果语法树// → mysql_execute_command(thd);}# 步骤目标用 GDB 追踪一条 SELECT 的解析过程gdb ~/mysql-install/bin/mysqld(gdb)breakmysql_parse(gdb)run --defaults-fileetc/my.cnf--gdb# 在另一个终端连接并执行 SQLmysql-uroot-S/tmp/mysql.sock-eSELECT 1# GDB 在 mysql_parse 处暂停(gdb)print thd-query()# 输出$1 {str SELECT 1, length 8}(gdb)next# 单步执行词法分析(gdb)print lex-sql_command# 输出SQLCOM_SELECT 解析器已识别出这是 SELECT 命令(gdb)continue# 继续执行完这条 SQL步骤二理解 lex.h——关键字到 token 的映射// 文件sql/lex.h简化版// 所有 SQL 关键字的符号表staticconstSYMBOL symbols[]{{SELECT,SYM(SELECT_SYM)},// SELECT → SELECT_SYM token{FROM,SYM(FROM)},// FROM → FROM token{WHERE,SYM(WHERE)},// WHERE → WHERE token{INSERT,SYM(INSERT)},// INSERT → INSERT token{UPDATE,SYM(UPDATE_SYM)},// UPDATE → UPDATE_SYM token{DELETE,SYM(DELETE_SYM)},// DELETE → DELETE_SYM token{SHOW,SYM(SHOW)},// SHOW → SHOW token// ... 共 900 个关键字};// 符号表哈希查找函数staticconstSYMBOL*get_hash_symbol(constchar*s,unsignedintlen){// 二分查找或哈希查找 symbols 数组// 返回对应的 SYMBOL 结构}-- 步骤目标理解关键字解析——查看关键字分类-- MySQL 中关键字的三种类型-- 1. 保留字ReservedSELECT、FROM、WHERE——不能用作标识符-- 2. 非保留关键字BEGIN、COMMENT——可以用作标识符不推荐-- 3. 函数名COUNT、SUM、MAX——在函数上下文中有特殊意义-- 查看所有关键字列表-- 在 MySQL 中-- mysql -e SELECT * FROM information_schema.KEYWORDS ORDER BY WORD;步骤三解读 sql_yacc.yy——Bison 语法规则/* 文件sql/sql_yacc.yy简化版 */ /* SELECT 语句的语法规则 */ select_stmt: SELECT select_options select_item_list FROM table_reference_list opt_where_clause opt_group_clause opt_having_clause opt_order_clause opt_limit_clause ; select_item_list: select_item | select_item_list , select_item ; select_item: * | expr opt_as_alias ; table_reference_list: table_reference | table_reference_list , table_reference ; opt_where_clause: /* empty */ | WHERE expr ; /* INSERT 语句的语法规则 */ insert_stmt: INSERT opt_into table_ident opt_field_list opt_values_or_select ; opt_values_or_select: VALUES values_row_list | select_stmt ;步骤四实现自定义 SHOW SLOW DIGEST 命令# # 步骤目标添加一个自定义 SQL 命令——SHOW SLOW DIGEST# # 实现步骤# 1. 在 lex.h 中添加关键字# 2. 在 sql_yacc.yy 中添加语法规则# 3. 创建 SQL 命令类# 4. 注册命令分派# 5. 编译 MTR 验证// Step 1: 在 sql/lex.h 中添加关键字 // 在 symbols 数组中添加// {SLOW, SYM(SLOW_SYM)},// {DIGEST, SYM(DIGEST_SYM)},// Step 2: 在 sql/sql_yacc.yy 中添加 token 声明和语法规则 /* %token SLOW_SYM %token DIGEST_SYM // 修改 show_stmt 的规则——添加新的 SHOW 类型 show_stmt: SHOW SLOW_SYM DIGEST_SYM { LEX *lex Lex; lex-sql_command SQLCOM_SHOW_SLOW_DIGEST; // 创建一个新的 SQL 命令对象 lex-m_sql_cmd new (thd-mem_root) Sql_cmd_show_slow_digest(); } ; */// Step 3: 创建新的 SQL 命令类 sql/sql_cmd_show_slow_digest.h /* class Sql_cmd_show_slow_digest : public Sql_cmd { public: bool execute(THD *thd) override { // 执行逻辑查询 Performance Schema 的慢 SQL 摘要 // SELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT // FROM performance_schema.events_statements_summary_by_digest // ORDER BY AVG_TIMER_WAIT DESC LIMIT 10 return send_slow_digest_result(thd); } enum_sql_command sql_command_code() const override { return SQLCOM_SHOW_SLOW_DIGEST; } }; */// Step 4: 在 sql/sql_parse.cc 中注册命令分派 // 在 mysql_execute_command() 的 switch 中添加/* case SQLCOM_SHOW_SLOW_DIGEST: { res lex-m_sql_cmd-execute(thd); break; } */# Step 5: 重新编译并验证 cd~/mysql-src/build-debug cmake--build.-j$(nproc)# 用 MTR 测试新语法cd~/mysql-src/mysql-testcatt/show_slow_digest.testEOF SHOW SLOW DIGEST; EOF# 生成预期结果文件首次运行./mtr--recordt/show_slow_digest# 验证测试通过./mtr t/show_slow_digest步骤五追踪解析器内部状态——用 YYDEBUG 输出解析过程# 步骤目标开启 Bison 调试输出——观察解析器的每一步# 重新编译——开启 YYDEBUGcd~/mysql-src/build-debug cmake..-DWITH_DEBUG1-DCMAKE_CXX_FLAGS-DYYDEBUG1cmake--build.-j$(nproc)# 启动时设置调试标志~/mysql-install/bin/mysqld --debug-dbugd,parser_debug--console# 在另一个终端执行 SQLmysql-uroot-S/tmp/mysql.sock-eSELECT 1# 观察 error log 中的解析器调试输出# Starting parse# Entering state 0# Reading a token: Next token is token SELECT (SELECT_SYM)# Shifting token SELECT_SYM# Entering state 1234# Reading a token: Next token is token 1 (NUM)# ...# 能看到完整的 shift/reduce 过程3.3 测试验证# 1. 验证原始 SQL 解析正常工作~/mysql-install/bin/mysqld--help--verbose21|head-5# 应正常输出帮助信息说明解析器初始化正常# 2. 用 MTR 跑基础 SQL 测试——确保改动没破坏已有功能cd~/mysql-src/mysql-test ./mtr--externsocket$HOME/mysql-install/mysql.sock main.select# 3. 验证自定义命令如果已实现mysql-uroot-S$HOME/mysql-install/mysql.sock-eSHOW SLOW DIGEST;# 应返回慢 SQL 摘要列表# 4. 验证调试输出grepStarting parse$HOME/mysql-install/logs/error.log4. 项目总结优点 缺点维度优点缺点/局限Bison 语法声明式定义——规则清晰——易于添加新语法sql_yacc.yy 超过 2 万行——阅读和维护门槛高LEX Parse Tree解析结果统一为 LEX 对象——后续阶段无需关心 SQL 文本LEX 结构体庞大——包含所有可能 SQL 类型的字段命令分派模式每种 SQL 命令独立一个 Sql_cmd 子类——解耦新增命令需要修改 4-5 个文件——步骤多但模板化MTR 语法测试自动化回归——每次改动自动验证需要为每个新语法编写 .test 和 .result 文件适用场景自定义 SQL 语法添加诊断命令、业务专用语法、兼容其他数据库的语法糖。SQL 审计插件在解析阶段拦截特定类型的 SQL——记录或阻断。SQL Rewrite 中间件基于解析树做 SQL 改写在 LEX 上修改查询条件。解析性能分析定位大型 SQL 语句的解析瓶颈。编译原理学习MySQL 的 Bison 语法是一个完整的工业级解析器案例。不适用场景简单的 SQL 文本替换不需要解析——用正则或字符串替换即可。不需要新语法的业务不需要修改解析器——标准 SQL 已足够。注意事项修改 sql_yacc.yy 后必须重新生成 C 文件CMake 会自动调用 bison 重新生成sql_yacc.cc和sql_yacc.h。新增关键字可能与已有标识符冲突如果关键字名称已被某些表名或列名使用——会导致已有 SQL 解析失败。保留字不能用作标识符不加引号新增保留字需要慎之又慎。常见踩坑经验故障案例一添加新语法后——已有的存储过程全部执行失败。根因新增的关键字恰好是某个存储过程的参数名——导致解析冲突。修复将新关键字设为非保留关键字——允许在特定上下文中用作标识符。故障案例二Bison 报告shift/reduce conflict。根因新增的语法规则和现有规则有歧义——Bison 不知道用哪条规则。修复用%expect声明预期的冲突数量或用%prec指定优先级解决歧义。故障案例三自定义命令执行时报Unknown command。根因在sql_yacc.yy中设置了sql_command——但忘记在sql_parse.cc的mysql_execute_command()的switch中添加对应case。修复补充 case 分支处理。思考题为什么 MySQL 的sql_yacc.yy中有大量%glr-parser相关的代码GLR 解析器和普通 LALR 解析器有什么区别如果你要添加一个KILL QUERY IF EXISTS语法——需要修改哪些文件列出清单。答案提示第 1 题——MySQL 8.0 引入了 GLRGeneralized LR解析器处理某些需要更多前瞻符号才能确定的语法——比 LALR 更强大但速度稍慢第 2 题——lex.h添加 IF/EXISTS 关键字、sql_yacc.yy修改 kill_stmt 规则、sql_parse.cc处理新的 case、sql_cmd.h新增命令类。延伸阅读与资源10倍开发者的 Dify 魔法书从零构建全栈 AI 应用后端工程师转型AI第一课-Ollama 与私有化大模型实战大型语言模型(LLM) vLLM 高性能推理落地实战Agent开发之LlamaIndex 实战修炼与源码进阶大语言模型Transformers 实战修炼与源码剖析
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Claude Code 的 skills 配置怎么接 TaoToken:settings.json 骨架与验证步骤 2026/9/28 6:36:37

Claude Code 的 skills 配置怎么接 TaoToken:settings.json 骨架与验证步骤

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
CLI-Anything vs OpenCLI 对比分析:TaoToken 统一 Key 下 AI Agent 操控 CLI 的两条路线 2026/9/28 6:36:37

CLI-Anything vs OpenCLI 对比分析:TaoToken 统一 Key 下 AI Agent 操控 CLI 的两条路线

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
护网行动|红队、蓝队完整详解 2026/9/28 6:36:36

护网行动|红队、蓝队完整详解

护网行动|红队、蓝队完整详解 一、什么是护网行动? 护网行动是以公安部牵头的,用以评估企事业单位的网络安全的活动。 具体实践中,公安部会组织攻防两方,进攻方会在一个月内对防守方发动网络攻击,检测出防守…

阅读更多 →
选北京模板网站开发公司5大注意事项 2026/9/28 6:36:36

选北京模板网站开发公司5大注意事项

选北京模板网站开发公司5大注意事项 网站被黑挂马后,很多老板第一反应是找黑客,其实大错特错。真正能救命的是快速隔离、溯源和加固。找北京模板网站开发公司时, 注意事项…

阅读更多 →
微信小程序开发必备的八个插件:用 TaoToken 统一管理 Key 与配置文件 2026/9/28 6:36:35

微信小程序开发必备的八个插件:用 TaoToken 统一管理 Key 与配置文件

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
大模型实操入门:用 TaoToken 统一 Key 跑通第一个对话 Demo 2026/9/28 6:36:29

大模型实操入门:用 TaoToken 统一 Key 跑通第一个对话 Demo

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉