你的 SQL 跑得很慢,你习惯性地打出EXPLAIN SELECT ...。
屏幕上弹出一个表格,id全是 1,Extra里写着Using temporary; Using filesort。
你的困惑:
到底是先 Join 再排序,还是先排序再 Join?
这个子查询是在哪里被执行的?
到底哪一步才是性能瓶颈?
那张平铺的表格无法直观地告诉你**“执行顺序”**。
新时代解法:
在 SQL 前加上EXPLAIN FORMAT=TREE。
MySQL 会吐出一棵“树”。就像看代码的缩进一样,谁包着谁,谁先执行,一目了然。
1. 核心原理:Volcano 模型 (火山模型)
MySQL 8.0 的执行器重构为了Iterator(迭代器)模式(也称为火山模型)。
每一个操作(如扫描全表、走索引、排序、Join)都是一个 Iterator。
数据像水流一样,从底层的 Iterator(叶子节点)被抽取出来,一层层向上流动,经过过滤、排序、聚合,最终汇聚到顶层(根节点)返回给用户。
TREE 格式就是这个“流水线”的直接映射。
- 缩进越深,越早执行(通常是数据的源头)。
- 缩进越浅,越晚执行(通常是数据的加工者)。
- 数据流向:从下往上,从内向外。
2. 实战演练:如何读懂这棵树?
案例:查找每个部门薪资最高的员工
SQL:
EXPLAINFORMAT=TREESELECTd.dept_name,e.name,e.salaryFROMemployees eJOINdepartments dONe.dept_id=d.idORDERBYe.salaryDESC;TREE 输出解析:
-> Sort: e.salary DESC (cost=1050.00 rows=1000) -> Stream results (cost=1050.00 rows=1000) -> Nested loop inner join (cost=1050.00 rows=1000) -> Table scan on e (cost=100.00 rows=1000) -> Single-row index lookup on d using PRIMARY (id=e.dept_id) (cost=0.25 rows=1)阅读顺序(由内而外,由下而上):
- 第一步 (最里层):
Table scan on e。全表扫描employees表。 - 第二步:
Nested loop inner join。拿到 e 表的数据,去和 d 表做 Join。 - 第三步:
Single-row index lookup on d。在 Join 过程中,使用主键索引查找departments表。 - 第四步 (最外层):
Sort: e.salary DESC。将 Join 好的结果集进行排序。
结论:一眼就能看出,性能瓶颈可能在于最内层的Table scan(全表扫描)以及最外层的Sort(如果内存不够会走磁盘)。
3. 三大实战场景
场景一:分辨 Hash Join (MySQL 8.0 杀手锏)
在老版本 EXPLAIN 中,你很难看出 Join 到底是用笨重的“嵌套循环 (Block Nested Loop)”还是高效的“哈希连接 (Hash Join)”。
TREE 格式一目了然:
-> Inner hash join (no condition) (cost=...) -> Table scan on t1 (cost=...) -> Hash -> Table scan on t2 (cost=...)解读:明确告诉你这是Hash Join。且结构显示,MySQL 先扫描t2并在内存中建立Hash 表(-> Hash),然后再扫描t1去探测这个 Hash 表。谁是驱动表,谁被 Hash,清清楚楚。
场景二:子查询是“物化”了还是“相关”了?
子查询优化是 SQL 调优的难点。
- 情况 A (Materialize):子查询只执行一次,结果存临时表。
TREE 显示:-> Materialize。 - 情况 B (Dependent):外层每有一行,子查询就执行一次(性能杀手)。
TREE 显示:-> Dependent subquery嵌套在 Loop 内部。
这种层级关系在老表格里很难体现,但在树形结构里,你一眼就能看到子查询是不是被“包”在循环里。
场景三:配合EXPLAIN ANALYZE(终极武器)
EXPLAIN只是“预估”,EXPLAIN ANALYZE才是“实测”。它会真的运行 SQL,并把实际耗时打印在树上。
EXPLAINANALYZESELECT*FROMusersWHEREage>20;输出:
-> Filter: (users.age > 20) (cost=... actual time=0.050..0.090 rows=10 loops=1) -> Table scan on users (cost=... actual time=0.040..0.080 rows=100 loops=1)解读:
- Cost:优化器估算的代价。
- Actual time:真实运行耗时(第一行拿到数据的时间…最后一行拿到数据的时间)。
- Loops:这个步骤循环执行了多少次。
通过对比Cost(估算)和Actual(真实),你能立刻发现统计信息是否过期,或者索引是否选错。
4. 总结
EXPLAIN FORMAT=TREE不是简单的格式转换,它是思维方式的升级。
- 老表格关注的是“有哪些表,用了什么索引”。
- 新树图关注的是“数据是怎么流动的,操作符是怎么组合的”。
如果你还在盯着id=1, select_type=SIMPLE发呆,赶紧换成FORMAT=TREE吧。一旦习惯了上帝视角,你就再也回不去那个“盲人摸象”的时代了。