MySQL 8.0Notes

第 09 章:执行器

zjc 于 2026-01-09 发布

这是《MySQL 8.0 源码与内核实战》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 优化器生成计划后,执行器负责驱动迭代器、调用 handler 接口、计算表达式、执行聚合排序,并处理写入路径。MySQL 8.0 的执行模型相比老版本更明显地转向迭代器式组织。

9.1 执行模型

Query_expression
  -> iterator root
     |-- table scan iterator
     |-- index range iterator
     |-- filter iterator
     |-- join iterator
     |-- aggregate iterator
     |-- sort iterator
     +-- limit iterator

每个迭代器常见接口:

init()
read()
set_key()
close()

执行时由上层节点驱动下层节点读取行,再应用过滤和投影。

9.2 读路径

Server 层:

Executor iterator
  -> handler::ha_index_read / ha_rnd_next
     -> ha_innobase
        -> row_search_mvcc
调用 场景
ha_rnd_next 全表或临时表顺序读
ha_index_first 索引第一条
ha_index_next 索引顺序读
ha_index_read_map 按 key 查找
ha_index_next_same 相同前缀继续读

9.3 表达式计算

表达式由 Item 树执行:

WHERE a + 1 > 10 AND b = 'x'
  -> Item_cond_and
     |-- Item_func_gt
     |   |-- Item_func_plus
     +-- Item_func_equal

执行关注:

  1. NULL 语义;
  2. 类型转换;
  3. 字符集排序;
  4. 短路求值;
  5. 函数确定性;
  6. 错误传播;
  7. 资源消耗。

9.4 过滤与投影

SELECT user_id, amount
FROM orders
WHERE status=1;

执行流程:

read row
  -> evaluate status condition
     -> if false skip
        -> evaluate user_id, amount
           -> send to client / upper iterator

投影影响:

  1. 返回字段;
  2. 覆盖索引可能性;
  3. 网络流量;
  4. 临时表列;
  5. 结果元数据。

9.5 聚合与分组

SELECT user_id, COUNT(*), SUM(amount)
FROM orders
GROUP BY user_id;

两种路径:

路径 条件
流式聚合 按分组列顺序读取,索引顺序匹配
临时表聚合 无序数据或内存/磁盘临时表

8.0 移除了查询缓存,但临时表引擎和聚合执行仍在持续演进。查看:

EXPLAIN FORMAT=TREE SELECT ...;
SHOW STATUS LIKE 'Created_tmp%';
SHOW STATUS LIKE 'Sort%';

9.6 排序与 LIMIT

SELECT * FROM orders
ORDER BY created_at DESC
LIMIT 20;

可能方式:

  1. 使用索引顺序;
  2. priority queue top N;
  3. 内存 sort buffer;
  4. 文件排序;
  5. 磁盘临时文件合并。

相关参数:

参数 说明
sort_buffer_size 每会话排序缓冲
max_length_for_sort_data 影响附加列策略
max_sort_length 字符串排序长度

9.7 写入路径

INSERT:

mysql_insert
  -> write_row
     -> handler::ha_write_row
        -> ha_innobase::write_row
           -> row_insert_for_mysql

UPDATE:

read row
  -> evaluate set expressions
     -> handler::ha_update_row
        -> InnoDB row update
           -> undo / redo / index maintenance

DELETE:

read row
  -> handler::ha_delete_row
     -> InnoDB delete mark
        -> purge later

9.8 多表更新

UPDATE orders o
JOIN users u ON u.id=o.user_id
SET o.level=u.level
WHERE u.status=1;

执行器先按计划读取满足连接条件的行,再逐行计算 SET 表达式并调用更新。触发器、外键和唯一索引都会增加写入路径复杂度。

9.9 执行观测

SELECT event_name, count_star, sum_timer_wait
FROM performance_schema.events_statements_summary_by_digest
ORDER BY sum_timer_wait DESC
LIMIT 10;

EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id=100;

EXPLAIN ANALYZE 能查看实际行数和每个迭代器耗时,适合与估算计划对比。

9.10 常见问题

现象 排查
估算行数差异大 统计和 trace
临时表暴涨 分组、排序、派生表
排序落盘 sort buffer 和索引
回表过多 覆盖索引和投影
写入慢 唯一检查、redo、锁
LIMIT 仍慢 前置扫描量

本章小结

执行器把计划转换为迭代器驱动和 handler 调用,负责过滤、投影、连接、聚合、排序和写入。源码阅读应从 EXPLAIN FORMAT=TREE 的节点入手,找到对应迭代器,再进入 InnoDB 行操作。

思考题

  1. ha_rnd_nextha_index_read 的使用场景有什么不同?
  2. 聚合什么时候可以流式执行?
  3. 为什么投影列会影响覆盖索引?
  4. top N 排序可能使用什么策略?
  5. EXPLAIN ANALYZE 与普通 EXPLAIN 的区别是什么?