MySQL 采用经典的分层架构设计,自上而下可划分为三大部分:连接层(Connection Layer)、服务层 / 计算层(Server Layer)和存储引擎层(Storage Engine Layer)。
这种设计最核心的价值在于:通过可插拔的存储引擎接口(handler API),将 SQL 解析、优化、执行等计算逻辑与底层数据存储彻底解耦。无论底层使用的是 InnoDB、MyRocks、MEMORY 还是其他引擎,上层的计算流程保持完全一致。
本笔记聚焦于计算层——也就是 MyRocks 和 InnoDB(以及所有其他存储引擎)共用的这一部分。它包括 Parser(SQL 解析器)、Optimizer(查询优化器)、Executor(执行器)以及 handler 抽象接口等核心模块。理解这一层的运作机制,是深入理解 MySQL 行为和性能特征的基础。
┌─────────────────────────────────────────────────────────────┐
│ Connection Layer │
│ 连接管理 · 线程分配 · 身份验证 · 权限校验 │
├─────────────────────────────────────────────────────────────┤
│ Server Layer (计算层) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌───────────┐ │
│ │ Parser │→│ Resolver │→│Optimizer │→│ Executor │ │
│ │ 词法+语法 │ │ 语义解析 │ │ 查询优化 │ │ 查询执行 │ │
│ └──────────┘ └───────────┘ └──────────┘ └─────┬─────┘ │
│ │ │
│ handler API (抽象接口) │ │
├─────────────────────────────────────────────────────┼────────┤
│ Storage Engine Layer │ │
│ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ InnoDB │ │ MyRocks │ │ MEMORY │ │ 其他... │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
连接层(Connection Layer): 负责客户端的连接管理、线程分配、身份验证和权限校验。每个客户端连接对应一个线程(在传统的 per-thread 模型下),该线程持有一个 THD(Thread Handler Descriptor)对象,贯穿整个请求生命周期。
服务层 / 计算层(Server Layer): 这是 MySQL 的”大脑”。SQL 语句在这里被解析为抽象语法树(AST),经过逻辑变换和基于代价的优化后生成执行计划,再由执行器驱动完成数据的读取与计算。8.0 版本在该层引入了大量重构,包括全新的 Iterator 执行器、AccessPath 执行计划表示和 Hypergraph 优化器。
存储引擎层(Storage Engine Layer): 通过 handler 抽象类及 handlerton 结构体与上层交互。InnoDB、MyRocks、MEMORY 等引擎各自实现 handler 的虚函数(如 open()、rnd_next()、index_read() 等),完成实际的数据读写操作。
MySQL 源码仓库的结构组织清晰,对于计算层学习而言,最核心的目录是 sql/,几乎所有 Server Layer 的逻辑都在其中。
| 目录 / 文件 | 说明 |
|---|---|
sql/ | 计算层核心:Parser、Optimizer、Executor、handler 接口等 |
sql/sql_parse.cc | SQL 命令分发入口,dispatch_command() 所在 |
sql/sql_lex.h | 词法分析器与 LEX 结构体定义 |
sql/sql_yacc.yy | Bison 语法规则文件,定义 SQL 语法 |
sql/sql_optimizer.cc | JOIN::optimize() 主优化流程 |
sql/sql_executor.cc | 执行器核心,Iterator 树构建 |
sql/sql_resolver.cc | 名称解析与语义分析(resolve 阶段) |
sql/sql_select.cc | SELECT 语句的入口与协调 |
sql/handler.h / handler.cc | 存储引擎抽象接口 handler 类定义 |
sql/item*.cc | 表达式系统(Item 类层次结构) |
sql/join_optimizer/ | Hypergraph 新优化器(8.0.22+) |
sql/iterators/ | Iterator 执行器实现(8.0.18+) |
storage/innobase/ | InnoDB 存储引擎(本笔记不深入) |
storage/rocksdb/ | MyRocks 存储引擎(Facebook 分支) |
💡 阅读建议: 建议从
sql/sql_parse.cc的dispatch_command()函数开始追踪一条 SELECT 查询的完整生命周期。官方 Doxygen 文档(https://dev.mysql.com/doc/dev/mysql-server/latest/)是极佳的交叉引用工具。
MySQL 8.0 的一个重要变化是统一了代码风格。在此之前,Server 层和 InnoDB 层各自有不同的编码规范。8.0.11 起全面采用 Google C++ Style Guide,并使用 clang-format 进行格式化。同时代码从早期的 C/C++ 混合风格逐步向现代 C++14 标准靠拢,引入了更多 RAII、智能指针和类型安全的设计。
THD(Thread Handler Descriptor)是 MySQL 中最核心的上下文对象。每个客户端连接的线程都持有一个 THD 实例,它贯穿从连接建立到查询执行的整个过程,是计算层各模块之间传递状态的枢纽。
THD 定义在 sql/sql_class.h 中,是一个庞大的类。它承载了以下关键信息:
1) 连接与线程信息: 包括线程 ID、客户端连接的网络句柄(NET 结构)、当前数据库名、用户权限上下文(Security_context)等。通过线程局部变量 current_thd 可以在代码的任意位置高效访问当前线程的 THD 实例,无需全局锁。
2) 查询状态: 包括当前 SQL 命令类型(sql_command)、LEX 结构体(存储解析结果)、诊断区域(Diagnostics_area,用于 warning/error 管理)、以及查询执行过程中的各类临时状态。
3) 事务上下文: 包含事务隔离级别、当前事务状态、binlog 事件缓冲区等信息。THD 中的 ha_data[] 数组为每个存储引擎预留了私有数据槽位,引擎可以在其中存储自己的事务状态。
4) 内存管理: THD 管理着 MEM_ROOT 内存分配器,用于为当前查询分配临时内存。查询结束后,整个 MEM_ROOT 可以被整体释放,避免了大量细粒度的 free 操作。
当客户端发送一条 SQL 语句时,MySQL 内部经历以下关键步骤(以 SELECT 为例):
| # | 阶段 | 说明与关键函数 |
|---|---|---|
| 1 | 接收请求 | 网络层读取客户端数据包,调用 do_command() 进入命令处理循环 |
| 2 | 命令分发 | dispatch_command()(sql_parse.cc)根据 COM_QUERY 类型进入 SQL 处理路径 |
| 3 | 词法分析 | MYSQLlex() 将 SQL 文本切分为 token 序列 |
| 4 | 语法分析 | Bison parser(sql_yacc.yy)将 token 构建为 AST(LEX / Query_block 结构) |
| 5 | 语义解析 | Query_block::prepare()(sql_resolver.cc)进行名称解析、类型检查、权限验证 |
| 6 | 逻辑变换 | 等值传播、子查询优化、派生表合并、分区裁剪等变换 |
| 7 | 物理优化 | JOIN::optimize()(sql_optimizer.cc)生成执行计划:选择访问路径、确定连接顺序 |
| 8 | 代码生成 | 将执行计划转换为 AccessPath 树(8.0)或 QEP_TAB 数组(5.7) |
| 9 | 执行 | Iterator 执行器(8.0)或传统执行器逐行拉取数据,通过 handler 接口读取存储引擎数据 |
| 10 | 返回结果 | 通过网络协议将结果集发送回客户端,清理查询状态 |
核心调用链:
do_command() → dispatch_command() → mysql_parse() → mysql_execute_command() → Query_expression::execute() → Query_block::execute() → Iterator::Read()
MySQL 的 SQL 解析器由两个紧密配合的组件构成:词法分析器(Lexical Scanner)和语法分析器(Grammar Rules Module / Bison Parser)。与某些将 SQL 编译为字节码的系统不同,MySQL 的解析器直接将 SQL 文本转换为内存中的 C++ 数据结构(即内部的 AST 表示)。
词法分析器定义在 sql/sql_lex.h 和 sql/sql_lex.cc 中,核心是 MYSQLlex() 函数和 Lex_input_stream 类。它负责:
SELECT、标识符 column_name、字面量 42 等)词法分析器是手工编写的(非 Lex/Flex 生成),这给予了开发者更精细的控制能力,尤其在处理 MySQL 特有的语法特性(如反引号标识符、# 风格注释等)时尤为重要。
语法分析器使用 GNU Bison(YACC 的增强版本)生成。语法规则定义在 sql/sql_yacc.yy 文件中,这是 MySQL 源码中最大的单个文件之一,包含数百条产生式规则。
MySQL 8.0 在 Parser 层进行了重大重构,主要工作包括:
(1) 大幅减少语法歧义: 从 5.7 时代的 240+ 个 shift/reduce 冲突,通过系统性的规则合并和去重,显著降低了语法歧义数量。例如,将原先散布在 CREATE TABLE...SELECT、CREATE VIEW、普通 SELECT 等场景中的多套重复 SELECT 规则统一为一套。
(2) 分离 Parse-time 临时状态: 早期版本中,大量仅在解析阶段使用的临时变量被塞进了 LEX 结构体和 THD 中。8.0 将这些临时状态移入局部的 parse context 对象中,减少了 LEX 结构体的膨胀,也降低了不同语法规则间意外共享状态导致 bug 的风险。
解析后的语法树由 Query_expression 和 Query_block(8.0 之前分别叫 SELECT_LEX_UNIT 和 SELECT_LEX)交替组成。这两个类是理解 MySQL 查询内部表示的关键:
Query_block: 代表一个查询块(query block),对应一个简单的 SELECT...FROM...WHERE 结构。包含 select list、from 子句(table list)、where 条件、group by、having 等完整信息。
Query_expression: 代表一个查询表达式,可以包含多个 Query_block(通过 UNION 连接),也可以包含多层 ORDER BY/LIMIT。一个复杂的嵌套查询会形成 Query_expression 和 Query_block 交替嵌套的树状结构。
所有表达式(列引用、函数调用、常量等)由 Item 类层次结构表示。Item 是 MySQL 中最庞大的类继承体系之一,衍生出 Item_field(列引用)、Item_func(函数)、Item_sum(聚合函数)、Item_cond(AND/OR 条件)等数百个子类。
// AST 核心结构示例(简化)
class Query_expression {
Query_block *first_query_block(); // 第一个 query block
Item *m_limit; // LIMIT 表达式
AccessPath *m_root_access_path; // 最终执行计划 (8.0)
unique_ptr<RowIterator> m_root_iterator; // 执行器入口 (8.0)
};
class Query_block {
mem_root_deque<Item *> fields; // SELECT 列表
Table_ref *table_list; // FROM 子句
Item *where_cond(); // WHERE 条件
ORDER *group_list; // GROUP BY
Item *having_cond(); // HAVING
JOIN *join; // 优化器/执行器入口
};
以下面的 SQL 为例,展示 Parser 的输出结构:
(SELECT * FROM t1)
UNION ALL
(SELECT * FROM (SELECT * FROM t2) AS a,
(SELECT * FROM t3 UNION ALL SELECT * FROM t4) AS b)
解析后会形成如下的嵌套结构:
Query_expression (顶层 UNION ALL)
├── Query_block: SELECT * FROM t1
└── Query_block: SELECT * FROM a, b
├── a → Query_expression
│ └── Query_block: SELECT * FROM t2
└── b → Query_expression (UNION ALL)
├── Query_block: SELECT * FROM t3
└── Query_block: SELECT * FROM t4
Parser 生成的 AST 只是语法结构的忠实反映,许多语义层面的工作(名称解析、类型推断、权限检查等)需要在 resolve 阶段完成。这一阶段的入口是 Query_block::prepare(),定义在 sql/sql_resolver.cc 中。
Query_block::prepare() 按照以下顺序执行(按功能分组而非严格执行序):
| 函数名 | 功能说明 |
|---|---|
setup_tables() | 设置查询块中的表叶节点,建立表引用 |
resolve_placeholder_tables() | 解析派生表(Derived Table)、视图(View)引用 |
merge_derived() | 尝试将简单的派生表合并进外层查询 |
setup_natural_join_row_types() | 处理 NATURAL JOIN / USING 的列匹配 |
setup_wild() | 展开 SELECT * 为具体的列引用列表 |
setup_fields() | 验证所有列引用存在且可访问,填充 Field 信息 |
setup_conds() | 解析 WHERE 条件和 JOIN 条件 |
setup_group() | 处理 GROUP BY 列表 |
resolve_rollup() | 处理 GROUP BY ... WITH ROLLUP 相关的 Item 替换 |
fix_fields() | 递归地对 Item 树进行类型推断和常量折叠 |
fix_fields() 是 Item 类的核心虚函数,在 resolve 阶段被递归调用。每个 Item 子类在 fix_fields() 中完成自身的语义校验和类型推断。例如:
Item_field::fix_fields() 将列名解析为具体的 Field 对象引用Item_func::fix_fields() 检查参数类型、确定返回值类型Item_sum::fix_fields() 注册聚合函数到所属的 Query_block这一机制保证了所有表达式在进入优化阶段之前都已完成类型确定和合法性验证。
一个值得注意的改进是:从 MySQL 5.7.4 开始,所有数据无关的永久性变换(如子查询优化重写、连接顺序调整等)都在 prepare 阶段完成,而 execute 阶段仅处理与具体绑定参数相关的变换。这一设计让 Prepared Statement 的重复执行更加高效。
MySQL 不缓存执行计划——每次执行都会重新优化。但由于 prepare 阶段的变换是持久化的,后续优化的起点更高,避免了数据倾斜问题。
MySQL 的查询优化器是计算层中最复杂的模块。它的核心任务是:给定一棵语义正确的查询树,找到一个资源消耗最小的物理执行计划。优化器的入口是 JOIN::optimize(),定义在 sql/sql_optimizer.cc 中。
逻辑变换(Logical Transformation): 在不改变查询语义的前提下,对查询进行等价重写,使其更易于后续的物理优化。这包括:
COUNT(*)/MIN()/MAX() 的常量替换)物理优化(Physical Optimization): 基于代价模型(Cost-Based Optimization)选择具体的执行策略。核心决策包括:
传统优化器在 JOIN::optimize() 中按以下顺序工作:
1) 逻辑变换阶段 —— optimize_cond、prune_table_partitions、optimize_aggregated_query、substitute_gc 等:对条件进行等值传播和常量折叠,裁剪无关的分区,识别可直接从索引获取的聚合结果。
2) 连接顺序选择 —— make_join_plan / Optimize_table_order::choose_table_order:使用贪心搜索(Greedy Search)结合有限深度的穷举搜索来确定多表连接的最优顺序。通过启发式剪枝来控制搜索空间的爆炸。
3) 后连接优化 —— substitute_for_best_equal_field、make_join_query_block、optimize_distinct_group_order 等:确定表条件的最优形式,注入外连接保护条件,尝试优化掉不必要的排序或去重。
4) 代码生成 —— alloc_qep、make_join_readinfo、make_tmp_tables_info:将优化结果转换为可执行的数据结构——在 5.7 中是 QEP_TAB 数组,在 8.0 中进一步转换为 AccessPath 树和 Iterator 树。
JOIN::optimize() 流程总览
┌──────────────────────────────┐
│ Logical Transformations │ optimize_cond / prune_table_partitions
│ (逻辑变换) │ optimize_aggregated_query / substitute_gc
├──────────────────────────────┤
│ Join Order Optimization │ make_join_plan → Greedy Search
│ (连接顺序) │ choose_table_order
├──────────────────────────────┤
│ Post-Join Optimization │ substitute_for_best_equal_field
│ (后连接优化) │ optimize_distinct_group_order
├──────────────────────────────┤
│ Code Generation │ alloc_qep → QEP_TAB (5.7)
│ (代码生成) │ → AccessPath → Iterator (8.0)
└──────────────────────────────┘
MySQL 5.7 引入了正式的 Cost Model API(WL#7182),将之前散布在代码中的硬编码代价常数统一收归到 Cost_model_server 和 Cost_model_table 两个类中。
代价计算考虑的核心因素:
代价常数可通过 mysql.server_cost 和 mysql.engine_cost 系统表进行配置,使得 DBA 可以根据实际硬件特性调整优化器的决策偏好。
从 MySQL 8.0.22 开始,官方引入了全新的基于 Hypergraph 的连接优化器,这是 MySQL 优化器架构的一次质的飞跃。
设计动机: 传统优化器只能生成左深树(left-deep tree)形式的连接计划,用 QEP_TAB 数组来表示。而 Hypergraph 优化器能够探索 bushy tree(灌木型树)等更丰富的计划空间,使用 HyperNode / HyperEdge 图结构来表示执行计划。
DPhyp 算法: 核心搜索算法基于论文 “Dynamic Programming Strikes Back” 中提出的 DPhyp 算法。该算法将查询中的关系视为节点,连接谓词视为超边(hyperedge),通过动态规划枚举所有连通子集来找到最优连接顺序。
EnumerateAllConnectedPartitions 函数中CostingReceiver 类负责基于代价评估和选择最优子计划AccessPath 抽象: Hypergraph 优化器直接输出 AccessPath 树,不再依赖 QEP_TAB。AccessPath 是 MySQL 8.0 引入的统一执行计划表示,它作为优化器输出和执行器输入之间的解耦层。无论使用旧优化器还是新的 Hypergraph 优化器,最终都会生成 AccessPath,然后通过 CreateIteratorFromAccessPath() 函数转换为 Iterator 执行器树。
传统优化器 Hypergraph 优化器
┌───────────┐ ┌──────────────────┐
│ JOIN:: │ │ FindBestQueryPlan│
│ optimize()│ │ (DPhyp算法) │
└────┬──────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────┐
│ AccessPath 树 │
│ (统一的执行计划中间表示) │
└───────────────────┬──────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ CreateIteratorFromAccessPath() │
│ → RowIterator 树 │
└──────────────────────────────────────────────┘
⚠️ 注意: Hypergraph 优化器最初以实验特性引入,并非所有查询都能使用。不支持的查询会自动退回到传统优化器。可通过
SET optimizer_switch='hypergraph_optimizer=on'启用。
理解优化器决策的最佳工具是 Optimizer Trace:
SET optimizer_trace = 'enabled=on';
SELECT ... ; -- 执行你的查询
SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE\G
SET optimizer_trace = 'enabled=off';
Optimizer Trace 会输出优化器为每条查询做出的所有决策过程,包括考虑过的访问路径、代价估算、选择/放弃的原因等。这对于调试查询性能问题和学习优化器行为都极有价值。
执行器是查询处理流水线的最后一环——它根据优化器生成的执行计划,驱动实际的数据读取与计算。MySQL 在执行器层面经历了从 5.7 到 8.0 的一次根本性架构升级。
传统执行器基于 QEP_TAB 数组驱动。核心结构 JOIN 持有一组按连接顺序排列的 QEP_TAB 对象,每个 QEP_TAB 描述了一张表的访问方式(全表扫描、索引查找、索引范围扫描等)。
执行器通过层层嵌套的循环来实现连接——嵌套循环连接(Nested Loop Join)是硬编码在执行器中的,这限制了执行策略的灵活性。
传统执行器的几个核心痛点:
QEP_TAB 数组只能表示左深树,无法表示 bushy tree从 MySQL 8.0.18 开始,MySQL 引入了全新的 Iterator 执行器框架,采用经典的 Volcano 模型(迭代器模型)。每个算子都是一个 RowIterator 子类,提供统一的接口:
class RowIterator {
// 初始化/重置迭代器,可多次调用以实现 rewind
virtual bool Init() = 0;
// 读取下一行,返回 0(成功) / -1(EOF) / 1(错误)
virtual int Read() = 0;
virtual ~RowIterator() = default;
};
查询的执行计划被表示为一棵 Iterator 树。顶层 Iterator 不断调用 Read(),每个 Iterator 根据需要向其子 Iterator 拉取数据(pull-based model)。
| Iterator | 功能 |
|---|---|
TableScanIterator | 全表顺序扫描 |
IndexScanIterator | 沿索引进行全扫描 |
IndexRangeScanIterator | 索引范围扫描(封装 QUICK_SELECT_I) |
RefIterator | REF 类型的索引查找(等值匹配) |
EQRefIterator | EQ_REF 类型的唯一索引查找 |
NestedLoopIterator | 嵌套循环连接(内连接、外连接、反连接) |
HashJoinIterator | Hash 连接(8.0.18+ 新增) |
FilterIterator | WHERE / HAVING 条件过滤 |
AggregateIterator | 聚合函数计算与分组 |
SortingIterator | 排序(在 Init() 中完成排序,Read() 中返回结果) |
LimitOffsetIterator | LIMIT / OFFSET 控制 |
MaterializeIterator | 物化:将子 Iterator 结果写入临时表后重新读取 |
StreamingIterator | 流式传输中间结果 |
WindowingIterator | 窗口函数计算 |
可组合性: 每个 Iterator 都是独立的、可组合的算子。Hash Join、Sort、Aggregate 等都是与 Nested Loop 平等的”一等公民”,可以自由嵌套组合。
Bushy Tree 支持: Iterator 树天然支持 bushy join(灌木型连接),这是 Hypergraph 优化器的前提条件。
EXPLAIN ANALYZE: 由于整个执行过程由 Iterator 树建模,可以在每个 Iterator 上插入计时/计数逻辑,从而实现 EXPLAIN ANALYZE(8.0.18 引入),显示每个执行步骤的实际行数和耗时。
EXPLAIN ANALYZE SELECT * FROM t1 JOIN t2 ON t1.id = t2.id WHERE t1.x > 10\G
-- 输出示例:
-- -> Nested loop inner join (cost=4.70 rows=10) (actual time=0.100..0.250 rows=8 loops=1)
-- -> Filter: (t1.x > 10) (cost=2.50 rows=10) (actual time=0.050..0.100 rows=15 loops=1)
-- -> Table scan on t1 (cost=2.50 rows=100) (actual time=0.030..0.080 rows=100 loops=1)
-- -> Single-row index lookup on t2 using PRIMARY (id=t1.id) (actual time=0.008..0.008 rows=0.53 loops=15)
AccessPath 是 MySQL 8.0.22 引入的关键抽象。它是一个轻量级的结构体,用来描述执行计划中的一个节点(一种数据访问或处理方式)。一个 AccessPath 与一个 Iterator 一一对应。
// 简化示意
unique_ptr<RowIterator> CreateIteratorFromAccessPath(
THD *thd,
AccessPath *path,
JOIN *join,
bool eligible_for_batch_mode)
{
switch (path->type) {
case AccessPath::TABLE_SCAN:
return make_unique<TableScanIterator>(...);
case AccessPath::INDEX_SCAN:
return make_unique<IndexScanIterator>(...);
case AccessPath::NESTED_LOOP_JOIN:
return make_unique<NestedLoopIterator>(...);
case AccessPath::HASH_JOIN:
return make_unique<HashJoinIterator>(...);
// ...
}
}
这一层抽象的引入实现了优化器和执行器的彻底解耦:优化器只需要输出 AccessPath 树来描述”做什么”,而不需要关心 Iterator 的具体实现(“怎么做”)。这使得未来可以更容易地替换或增强优化器与执行器的任一方。
Handler API 是 MySQL 可插拔存储引擎架构的基石。它定义了计算层与存储引擎之间的契约——一组标准化的虚函数接口。无论底层是 InnoDB、MyRocks 还是 MEMORY 引擎,计算层都通过相同的 handler 接口与之交互。这正是 MyRocks 能够复用 MySQL 整个计算层的原因。
handler 类定义在 sql/handler.h 中,是所有存储引擎接口的基类。每个存储引擎都实现一个 handler 子类(如 ha_innobase、ha_rocksdb 等)。handler 提供的核心方法可分为以下几类:
| 类别 | 关键方法 | 说明 |
|---|---|---|
| 表操作 | open() / close() | 打开/关闭一张表 |
| 全表扫描 | rnd_init() / rnd_next() | 初始化顺序扫描 / 读取下一行 |
| 索引扫描 | index_init() / index_read() | 初始化索引 / 按键值查找 |
| 索引遍历 | index_next() / index_prev() | 索引前进/后退 |
| 写入操作 | write_row() | 插入一行 |
| 更新操作 | update_row() | 更新当前行 |
| 删除操作 | delete_row() | 删除当前行 |
| 事务接口 | external_lock() | 语句级锁/事务边界通知 |
| 元信息 | info() / records() | 返回表统计信息(行数、索引信息等) |
| DDL | create() / truncate() | 创建/截断表 |
除了 handler 类(描述单个表实例的操作),MySQL 还使用 handlerton 结构体来描述整个存储引擎的全局属性和回调。handlerton 包含引擎级别的操作:
commit)、事务回滚(rollback)每个存储引擎在初始化时注册自己的 handlerton 到全局引擎列表中。
理解 handler API 的关键在于数据流转方式。MySQL 的计算层不直接操作存储引擎的内部数据格式,而是通过”行缓冲区”(record buffer)与引擎交互:
计算层 (Executor) 存储引擎 (handler子类)
│ │
│ ① 分配行缓冲区 │
│ TABLE::record[0] │
│ │
│ ② 调用 handler::rnd_next() ──────→│
│ │ ③ 引擎写入数据到
│ │ 行缓冲区
│ ④ 从行缓冲区提取列值 ←─────────────│
│ 通过 Field 对象读取 │
│ │
TABLE::record[0]),其格式由表的 Field 对象数组定义这种基于行缓冲区的交互模式虽然简单统一,但也意味着每次数据传递都涉及行格式的编解码,这在高吞吐场景下可能成为性能瓶颈。
MyRocks(ha_rocksdb)是 Facebook 基于 RocksDB 构建的 MySQL 存储引擎,它完美地利用了 handler API 与 MySQL 计算层集成。MyRocks 通过实现 handler 接口的各个虚函数,将 MySQL 的 SQL 能力嫁接到 RocksDB 的 LSM-Tree 键值存储之上。
数据映射: MyRocks 将每个索引(包括主键和二级索引)存储为 RocksDB 中的一个 Column Family。行数据按照主键编码为 RocksDB 的 key-value 对。当计算层通过 handler::index_read() 请求某个索引值时,MyRocks 将请求翻译为 RocksDB 的 Get() 或 Iterator::Seek() 操作。
MyRocks 相比 InnoDB 的核心优势:
Facebook 在 2017 年将其核心用户数据库(UDB)从 InnoDB 迁移到 MyRocks 后,实例存储大小减少了 62.3%,数据库服务器数量缩减至不到一半。
💡 理解要点: 计算层对存储引擎是透明的。无论使用 InnoDB 还是 MyRocks,
SELECT/INSERT/UPDATE/DELETE的解析、优化、执行流程完全相同——差异仅在 handler 接口以下。
Item 类层次结构是 MySQL 计算层中最庞大、最基础的子系统之一。SQL 语句中的每一个表达式——列引用、字面量、函数调用、运算符、子查询等——都由一个 Item 对象表示。理解 Item 系统是理解 MySQL 如何计算和传递数据的关键。
| 类名 | 说明 |
|---|---|
Item | 抽象基类,定义 val_int()、val_str()、val_real() 等求值接口 |
Item_field | 列引用,关联到 TABLE::Field 对象 |
Item_int / Item_string / … | 各类字面量常量 |
Item_func | 函数基类,衍生出 Item_func_plus、Item_func_concat 等 |
Item_cond | 逻辑组合条件(AND / OR) |
Item_sum | 聚合函数基类(SUM / COUNT / AVG / MIN / MAX 等) |
Item_subselect | 子查询表达式 |
Item_ref | 引用另一个 Item(如外层查询的列引用) |
Item_cache | 缓存求值结果,避免重复计算 |
Item (抽象基类)
├── Item_field 列引用
├── Item_int / Item_string / ... 字面量
├── Item_func 函数基类
│ ├── Item_func_plus 加法 (+)
│ ├── Item_func_concat 字符串连接
│ ├── Item_func_if IF 函数
│ ├── Item_cond AND / OR
│ │ ├── Item_cond_and
│ │ └── Item_cond_or
│ └── ... 数百个子类
├── Item_sum 聚合函数基类
│ ├── Item_sum_sum SUM()
│ ├── Item_sum_count COUNT()
│ ├── Item_sum_avg AVG()
│ └── ...
├── Item_subselect 子查询
├── Item_ref 间接引用
└── Item_cache 值缓存
每个 Item 提供多种求值方法,调用者根据需要选择合适的接口:
| 方法 | 返回类型 | 用途 |
|---|---|---|
val_int() | longlong | 整数值 |
val_real() | double | 浮点值 |
val_str() | String * | 字符串 |
val_decimal() | my_decimal * | 高精度小数 |
val_json() | Json_wrapper * | JSON 值(8.0 新增) |
null_value | bool | 指示结果是否为 NULL |
执行器在处理每一行时,通过调用 SELECT 列表中各 Item 的 val_xxx() 方法来获取当前行的列值。这种”拉取式”的求值模型与 Iterator 执行器的 Volcano 模型是天然匹配的。
MySQL 的类型系统围绕 enum_field_types 定义,包括 MYSQL_TYPE_LONG、MYSQL_TYPE_VARCHAR、MYSQL_TYPE_DATETIME 等数十种类型。Item 的 data_type() 方法返回其结果的数据类型。
在比较和运算时,MySQL 根据类型比较规则进行隐式转换——这是许多 SQL 行为(和陷阱)的根源。例如,当字符串与数字比较时,字符串会被隐式转换为数字,这可能导致索引失效。
当查询需要 GROUP BY、DISTINCT 或 ORDER BY,且无法通过索引直接满足时,MySQL 会创建内部临时表或执行文件排序(filesort)。
临时表: 实现在 sql/sql_tmp_table.cc 中,可以使用 MEMORY 引擎(内存哈希表或 B-Tree)或 InnoDB 引擎(当数据量超过 tmp_table_size 时自动转换)。
排序: 由 filesort() 函数实现(sql/filesort.cc)。它使用基于优先队列或归并排序的算法。当排序数据超过 sort_buffer_size 时,会溢出到磁盘,使用多路归并来完成排序。在 Iterator 执行器中,排序被封装为 SortingIterator,其 Init() 方法完成整个排序过程,后续的 Read() 调用逐行返回排序结果。
MySQL 优化器对子查询的处理策略非常丰富:
Semi-join 变换: 将 IN/EXISTS 子查询转化为半连接(semi-join),使其能够参与正常的连接顺序优化。具体策略包括:
Derived Table 合并: 简单的派生表可以被合并到外层查询中(merge_derived),避免物化的开销。
子查询物化: 将子查询结果物化到临时表中,之后通过索引查找来判断存在性或获取值。
MySQL 8.0 用事务性的 Data Dictionary(DD)替换了原来基于 .frm 文件的元数据存储。所有元数据(表定义、索引信息、用户权限等)统一存储在 InnoDB 表中。这使得 DDL 操作具有原子性——CREATE TABLE 或 ALTER TABLE 不再像以前那样可能留下不一致的状态。
尽管 DD 的底层依赖 InnoDB,但它的接口是存储引擎无关的,所有存储引擎的表元数据都通过统一的 DD API 管理。
MySQL 的复制机制依赖于 Binary Log(binlog),它记录了所有修改数据的操作。binlog 是在计算层生成的,与存储引擎无关——这也是 MyRocks 能够直接使用 MySQL 复制功能的原因。
binlog 支持 STATEMENT 和 ROW 两种格式。MyRocks 由于不支持 next-key locking,在主库上必须使用 ROW 格式。
复制的基本流程:
Master Slave
│ │
│ ① 计算层执行 DML │
│ ② 写入 binlog event │
│ │
│ ③ Slave I/O Thread 拉取 binlog ──→│
│ │ ④ 写入 Relay Log
│ │ ⑤ SQL Thread 重放事件
│ │ ⑥ 通过 handler API 写入本地引擎
对于初次接触 MySQL 源码的读者,建议按以下路径渐进式地深入:
第一阶段:跟踪一条查询的生命周期。 从 sql_parse.cc 的 dispatch_command() 入手,追踪一条简单的 SELECT 查询从接收到返回结果的完整路径。重点理解 mysql_execute_command()、Query_block::prepare()、JOIN::optimize() 和 Iterator::Read() 这几个关键节点。
第二阶段:理解核心数据结构。 深入 THD、LEX、Query_block、JOIN、handler、Item 等核心类的定义和关系。这些是贯穿整个计算层的骨架。
第三阶段:专题深入。 选择感兴趣的方向深入,如优化器的连接顺序选择算法、Iterator 执行器的各类算子实现、handler API 的具体调用时序等。
| 工具 | 用途 |
|---|---|
| Doxygen 文档 | https://dev.mysql.com/doc/dev/mysql-server/latest/ 提供源码交叉引用 |
| Optimizer Trace | SET optimizer_trace='enabled=on' 查看优化器决策细节 |
| EXPLAIN ANALYZE | 8.0.18+ 支持,显示实际执行时间和行数 |
| GDB / LLDB | 断点调试,跟踪代码执行流 |
| perf / flamegraph | 性能瓶颈定位,生成火焰图 |
| MySQL Debug Build | 编译时启用 --debug,支持 DBUG 宏追踪 |
| Performance Schema | 运行时内部状态监控(等待事件、锁、I/O 等) |