MySQL 并不是直接“看懂”你写的 SELECT * FROM user WHERE id = 1,而是先把它切成一个个有含义的单元,比如 SELECT
、*、FROM、user、WHERE、id、=、1。这个切分过程由 lex_token() 函数驱动,底层基于 sql/lex.h 和 sql/sql_lex.cc 中的词法规则。
常见错误现象:ERROR 1064 (42000): You have an error in your SQL syntax 往往就卡在这步——比如写成 SELECt(拼错)或 SELECT * FROM user where id==1(多了一个 =),词法分析器无法识别出合法 token,直接报错,根本不会进后续流程。
lower_case_table_names,但关键字(如 SELECT)始终不区分大小写-- 、/* */)在此阶段被剥离,不参与后续解析MYSQLparse()
词法分析输出一串 token 后,MySQL 调用 MYSQLparse()(定义在 sql/sql_yacc.yy,由 Bison 生成)进行语法树构建。它依据预定义的语法规则(类似 BNF)判断这些 token 的组合是否符合 MySQL 支持的 SQL 语法结构。
例如:SELECT * FROM (SELECT 1) t 能过词法关,但 SELECT * FROM (SELECT 1) 缺少别名,在语法分析阶段就会失败(ERROR 1327 (42000): Undeclared variable 或类似提示,具体取决于版本)。
sql_yacc.yy 是核心,修改它可扩展语法(极少见,且风险极高)db.table 中的 db),但不会查数据字典验证存在性AST 构建完成后,MySQL 进入 mysql_execute_command() 的早期分支,执行关键校验:
SELECT/INSERT 等对应权限(查 mysql.tables_priv 等系统表)user.id 映射到实际的 TABLE_LIST 和 Field 对象,此时才真正访问数据字典(dict_table_t 或 TABLE_SHARE)id)、不存在的列、聚合与非聚合混用等典型报错:ERROR 1054 (42S22): Unknown column 'xxx' in 'field list' 或 ERROR 1142 (42000): SELECT command denied to user —— 都发生在这里,而非网络收包或词法阶段。
语义无误后,MySQL 调用优化器模块(make_join_statistics()、choose_plan() 等),生成执行计划(EXPLAIN 输出的内容)。它决定:
JOIN order)WHERE 条件提前过滤)Using filesort)注意:优化器不保证“最优”,只求“足够好”。统计信息不准(ANALYZE TABLE 没跑)、小表误判为大表、隐式类型转换导致索引失效,都会让这一步产出低效计划。这也是为什么 EXPLAIN 必须在真实环境下看,不能只信开发库结果。
SELECT * FROM orders o JOIN customers c ON o.cust_id = c.id WHERE o.status = 'shipped' AND c.country = 'CN';
这条语句从接收到返回,中间至少经过:socket 读取 → 字符串拆解(lex)→ token 序列生成 → 语法树构建(yacc)→ 表/字段解析 + 权限校验 → 优化器估算成本并选索引 → 执行器拉取数据 → 结果包返回。任何一环失败,都停在那一步,不会“跳过错误继续执行”。
最容易被忽略的是:词法和语法分析完全内存操作,不碰磁盘;而语义分析开始就要查系统表、打开表结构,这时候才真正产生 I/O 和锁等待。线上慢查询如果卡在“Sending data”之前,大概率是语义层或优化器在挣扎。