mysql执行sql需经连接器、分析器、优化器、执行器四层处理:连接器固化权限快照;分析器完成词法、语法、语义分析;优化器生成代价最优执行计划;执行器调用存储引擎api执行,innodb仅接收底层指令而非sql文本。

SELECT * FROM users WHERE id = 123 这类语句,MySQL 不是直接扔给 InnoDB 去查的。它必须先“翻译”一遍——把人类写的逻辑 SQL,变成存储引擎能听懂的、带具体访问路径和数据结构的操作指令。这个过程不是黑盒,而是由服务层(Server Layer)里几个关键组件接力完成的。
连接器做完权限快照后,分析器才真正开始读 SQL
连接建立后,权限信息就固化在当前连接线程的内存中,后续所有操作都基于这个快照判断——改了 mysql.user 表也无效,除非重连。分析器这时才接手 SQL 字符串,分三步走:
- 词法分析:把
SELECT、FROM、users、WHERE、id、=、123拆成独立 token,识别出关键字、表名、列名、常量 - 语法分析:按 MySQL 语法规则组装成解析树(Parse Tree),确认结构合法,比如
SELECT后不能跟ORDER BY而没FROM - 语义分析:查
users表是否存在、id列是否属于该表、当前用户是否有SELECT权限——任一失败就报错,如Unknown table 'users'或SELECT command denied to user
优化器决定“怎么查”,而不是“查什么”
解析树通过后,优化器登场。它不关心业务含义,只算代价:走主键索引查 1 行 vs 全表扫描 10 万行,哪个更省 IO 和 CPU?它会做这些事:
- 逻辑重写:把
WHERE a = 1 AND b = 2拆成独立条件,方便后续评估选择性 - 索引评估:查
users表的统计信息(如INFORMATION_SCHEMA.STATISTICS),看id上有没有索引、选择性是否高(id是主键,必然选它) - 执行路径决策:对单表查询,基本就是“用哪个索引 + 回表还是覆盖”;对多表 JOIN,还要选连接算法(NLJ / Hash Join / SMJ)和驱动表顺序
- 生成执行计划:输出一个可执行的结构化指令序列,比如 “IndexScan on
usersusingPRIMARY, filter:id = 123”
注意:优化器不会执行任何数据读取,它只输出计划。你可以用 EXPLAIN FORMAT=TREE SELECT * FROM users WHERE id = 123 看到它生成的完整决策树。
执行器调用存储引擎接口,把计划变成真实动作
执行器拿到优化器的计划后,不再碰 SQL 文本,而是按指令调用存储引擎 API。对 InnoDB 来说,关键动作是:
- 权限再检:虽然连接器已验权,但执行器在真正读表前还会校验一次,防止中间权限变更(尽管连接内权限不变,这是安全兜底)
- 调用引擎接口:执行器调用
ha_innobase::index_read()(走索引)或ha_innobase::rnd_next()(全表扫描),传入的是内部数据结构(如KEY_PART_INFO描述索引字段),不是原始 SQL - 处理结果集:引擎返回的是行数据缓冲区(
record[]),执行器负责格式化(类型转换、NULL 处理)、应用WHERE剩余过滤(如果索引无法完全下推)、组装结果集 - 事务上下文传递:如果当前会话开了事务,执行器会把事务 ID(
trx_id)透传给 InnoDB,确保 MVCC 可见性判断正确
InnoDB 接收的是“定位+读取”指令,不是 SQL 字符串
InnoDB 完全不知道你写的是 SELECT 还是 UPDATE,它只接收执行器发来的底层操作请求。例如:
- 主键等值查询 → 调用
btr_pcur_open_at_rnd_pos()定位 B+ 树叶子页,再用row_search_mvcc()拿数据 - 二级索引查询 → 先查二级索引 B+ 树得主键,再回表查聚簇索引 —— 这两步都是执行器拆解后分别调用的
- INSERT → 执行器构造好记录 buffer,调
row_ins_clust_index_entry()让 InnoDB 插入并维护锁和日志
真正的“翻译”终点在这里:SQL 的逻辑意图,最终被切片成一个个针对 B+ 树、undo log、buffer pool 的 C 函数调用。这也是为什么换存储引擎(比如 MyISAM)时,只要实现同一套 handler 接口,Server 层代码完全不用改。











