这是《MySQL 8.0 源码与内核实战》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 MySQL 源码量大,直接逐行读效率很低。有效方法是问题驱动:从现象入手,建立调用链,再用调试器和测试验证。
3.1 问题驱动
阅读前先定义问题:
现象:SELECT 使用二级索引后为什么回表?
问题:InnoDB 在哪里从二级索引回到聚簇索引?
验证:断点 row_search_mvcc
输出:调用链和最小复现
适合入手的问题:
- 一条 SQL 的解析和执行路径;
- 一个执行计划为什么选择某个索引;
- 一个锁为什么被持有;
- 一个参数如何生效;
- 一个状态计数在哪里增加;
- 一个错误码在哪里返回。
3.2 从入口到出口
SELECT 调用链:
dispatch_command
-> mysql_parse
-> mysql_execute_command
-> open_tables
-> optimize
-> execute
-> handler::ha_rnd_next / ha_index_read
-> InnoDB row operations
UPDATE 调用链:
parse
-> resolve
-> optimize
-> scan/index read
-> row update
-> undo log
-> redo log
-> binlog
-> commit
先画粗链路,再深入分支。
3.3 静态搜索
常用搜索:
rg "dispatch_command" sql/
rg "class THD" sql/ include/
rg "row_search_mvcc" storage/innobase/
rg "SAVEPOINT" sql/ storage/innobase/
rg "ER_DUP_ENTRY" sql/ include/
搜索策略:
- 函数名;
- 错误码字符串;
- 状态变量名;
- 配置参数名;
- 注释中的 RFC 或 Worklog;
- MTR 测试名;
- 结构体字段。
3.4 调用栈验证
静态搜索会给出多个候选,调用栈能确认实际路径。
b row_search_mvcc
run
bt
#0 row_search_mvcc
#1 row_search_index_step
#2 row_search_for_mysql
#3 ha_innobase::index_read
验证内容:
- 入口函数;
- 参数含义;
- 分支条件;
- 返回值;
- 错误路径;
- 资源释放;
- 调用方。
3.5 阅读结构体
不要一开始读完所有字段,先看生命周期:
| 结构 | 生命周期 | 关注点 |
|---|---|---|
THD |
一个客户端线程 | security、query、transaction |
TABLE |
打开的表对象 | 字段、索引、触发器 |
handler |
一次表访问 | 引擎接口 |
row_prebuilt_t |
InnoDB 表操作上下文 | 游标、事务、模板 |
trx_t |
InnoDB 事务 | id、state、lock |
mtr_t |
mini transaction | 页修改和日志 |
字段含义可通过初始化、赋值和引用位置交叉验证。
3.6 版本对比
git log --oneline -- sql/sql_optimizer.cc
git blame -L 100,160 sql/sql_executor.cc
git show <commit> -- storage/innobase/lock/lock0lock.cc
版本对比适合:
- 参数默认值变化;
- 新执行计划输出;
- 锁实现重构;
- 数据字典改造;
- 性能优化引入的行为变化。
结论必须落到当前使用版本。
3.7 结合观测工具
| 工具 | 作用 |
|---|---|
EXPLAIN FORMAT=TREE |
执行计划 |
optimizer_trace |
代价和候选计划 |
performance_schema |
等待和阶段 |
SHOW ENGINE INNODB STATUS |
事务和锁 |
information_schema.innodb_trx |
当前事务 |
sys.innodb_lock_waits |
锁等待 |
| error log | 启动和异常 |
示例:
SET optimizer_trace="enabled=on";
SELECT * FROM t WHERE a=1;
SELECT trace FROM information_schema.optimizer_trace;
3.8 记录源码笔记
建议模板:
版本:mysql-8.0.40
问题:
复现 SQL:
调用链:
关键函数:
关键结构:
分支条件:
验证方式:
结论:
注意事项:
笔记要保存复现命令,不只是函数名。
3.9 常见误区
| 误区 | 后果 |
|---|---|
| 逐行读所有代码 | 快速失去全局 |
| 只看博客结论 | 版本不匹配 |
| 只看函数名 | 忽略状态和锁 |
| 不看调用方 | 误解生命周期 |
| 不写复现 | 无法验证 |
| 混淆 5.7 与 8.0 | 结论过时 |
| 忽略测试用例 | 遗漏边界行为 |
3.10 阅读路线
推荐顺序:
连接与线程
-> Parser / Resolver
-> Optimizer
-> Executor
-> handler
-> InnoDB page / B+Tree
-> Buffer Pool
-> trx / lock / undo
-> redo / recovery
-> background threads
先掌握数据路径,再深入恢复和复制。
本章小结
源码阅读要以现象为入口、调用链为主线、调试器和 MTR 为验证手段。搜索、断点、版本对比和观测工具结合,才能把源码结论变成可复现、可落地的内核理解。
思考题
- 为什么静态搜索不能替代调用栈?
- 阅读
THD时应先关注哪些生命周期信息? git blame在源码研究中的用途是什么?- optimizer trace 能帮助定位哪类源码问题?
- 如何写一份可复现的源码笔记?