这是《MySQL 8.0 源码与内核实战》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Parser 将 SQL 文本转换为解析树。它决定语法是否合法,并保留语句结构供语义解析和优化使用。
6.1 输入输出
SQL text
-> lexer
-> token stream
-> parser
-> Parse Tree
-> Resolver / Transformer
Parser 只处理语法和基本结构,不做:
- 表是否存在;
- 列类型是否匹配;
- 权限是否允许;
- 索引是否有效;
- 执行代价是否合理。
这些属于后续阶段。
6.2 相关源码
| 文件 | 职责 |
|---|---|
sql/sql_lex.cc |
词法与 LEX 相关逻辑 |
sql/sql_yacc.yy |
Bison 语法规则 |
sql/parser_yystype.h |
语义值类型 |
sql/parse_tree_* |
解析树节点 |
sql/sql_parse.cc |
解析入口 |
sql/sql_parser.cc |
Parser 封装 |
入口调用:
mysql_parse
-> parser_state initialization
-> parse_sql
-> generated parser
-> Parse_tree_root
6.3 词法
词法分析处理:
- 关键字;
- 标识符;
- 字符串;
- 数字;
- 注释;
- 字符集前缀;
- 运算符;
- 参数占位符。
示例:
SELECT id, name FROM users WHERE id = ?
会被切分为关键字、标识符和符号 token。
字符集会影响字符串 token 和标识符解析,这也是某些 SQL 在不同 character_set_client 下表现不同的原因。
6.4 语法规则
Bison 规则示意:
select_statement:
SELECT select_list
FROM table_references
WHERE expr
实际源码规则更复杂,包含:
- WITH CTE;
- window function;
- JOIN 语法;
- GROUP BY / HAVING;
- ORDER BY / LIMIT;
- 锁子句;
- UNION;
- 子查询;
- DML;
- DDL。
调试可从错误信息入手:
rg "syntax to use near" sql/
6.5 解析树节点
常见节点:
| 节点 | 对应语法 |
|---|---|
PT_select |
SELECT |
PT_table_factor |
表引用 |
PT_item_expr |
表达式 |
PT_join |
JOIN |
PT_window |
窗口 |
PT_insert |
INSERT |
PT_update |
UPDATE |
PT_delete |
DELETE |
PT_create_table |
CREATE TABLE |
解析树保留源码书写结构,后续阶段会继续规范化和改写。
6.6 预处理语句
PREPARE s FROM 'SELECT * FROM t WHERE id=?';
SET @id=1;
EXECUTE s USING @id;
DEALLOCATE PREPARE s;
流程:
prepare
-> parse
-> prepare_expr / resolve
-> keep statement structure
execute
-> bind parameter
-> optimize if needed
-> execute
注意 max_prepared_stmt_count、会话生命周期和 SQL 注入防护。
6.7 错误处理
常见错误:
| 错误 | 阶段 |
|---|---|
ER_PARSE_ERROR |
语法错误 |
ER_UNKNOWN_TABLE |
解析后语义解析 |
ER_BAD_FIELD_ERROR |
列解析 |
ER_SYNTAX_ERROR |
语法规则失败 |
ER_NOT_SUPPORTED_YET |
语法存在但特性不支持 |
解析错误要记录完整 SQL 和字符集环境。截断后的 SQL 可能无法复现。
6.8 调试 Parser
b mysql_parse
b parse_sql
run
bt
p parser_state
p thd->query().str
最小实验:
SELECT 1;
SELECT * FROM mysql.user LIMIT 1;
SELECT * FROM (SELECT 1) AS x;
观察不同语句生成的解析入口和树结构。
6.9 性能影响
解析通常不是最大瓶颈,但以下场景会放大:
- 超长 SQL;
- 大量 IN 列表;
- 深嵌套子查询;
- 频繁执行相同 SQL 但不使用 prepared statement;
- ORM 生成巨型查询;
- 每次都解析上千条批量值。
优化方向:
- 应用侧参数化;
- 拆分巨型 SQL;
- 减少不必要列;
- 批量写入分批;
- 缓存业务结果。
6.10 与优化器边界
Parser 输出的是“写了什么”,优化器决定“怎么做”。
SELECT * FROM t WHERE a=1;
Parser 只识别 SELECT、表和谓词;优化器再比较全表扫描、索引扫描和覆盖索引代价。
本章小结
Parser 负责把 SQL 文本转换为解析树,核心在词法、Bison 语法规则和 Parse Tree 节点。它不做权限和执行计划决策。读 Parser 时应从错误码、入口函数和最小语法用例切入。
思考题
- Parser 和 Resolver 的职责边界是什么?
- 字符集为什么会影响解析?
- prepared statement 在哪个阶段保存结构?
- 哪些 SQL 会放大解析成本?
- 如何通过
ER_PARSE_ERROR定位源码?