执行器通过ha_next()逐行调用存储引擎获取数据,引擎按页遍历并支持条件下推,server层对未下推条件二次过滤;列投影决定是否读溢出页;流式读取依赖客户端fetch size设置。

执行器调用 ha_next() 迭代器接口读取下一行
MySQL 执行器本身不直接读磁盘或解析页结构,它通过统一的存储引擎 API(如 ha_next())向 InnoDB 等引擎发起“取下一行”的请求。每次调用 ha_next(),引擎内部会推进游标、检查是否越界、触发必要 I/O,并返回一条逻辑记录。
这个过程不是“逐字节读行”,而是引擎在页内遍历已加载的记录:如果当前页还有未过滤的记录,就从中构造一行;若已到页尾,则加载下一页(可能触发同步磁盘读)再继续。
-
ha_next()是阻塞式调用,执行器必须等它返回才处理下一行 - 即使 SQL 写了
LIMIT 1,执行器仍会调用一次ha_next(),但之后不再继续 - 全表扫描时,InnoDB 可能按区(extent)预读多页,但
ha_next()的语义仍是“一行一行”暴露给执行器
WHERE 条件在两层过滤:引擎层先筛,Server 层补漏
执行器拿到引擎返回的每一行后,并不直接发给客户端——它要先判断是否满足 WHERE 条件。但关键点在于:哪些条件由引擎完成,哪些留到 Server 层?
InnoDB 能下推的条件(如 id = 10、status IN (1,2))会在读页后、构造记录前就跳过不匹配的记录,避免构造和传输;而含函数、类型转换或跨列计算的表达式(如 UPPER(name) = 'ABC')无法下推,只能等执行器拿到完整行后再计算。
- 看
EXPLAIN的Extra列:Using where出现,说明 Server 层有额外过滤 - Server 层过滤会增加 CPU 和内存拷贝开销,尤其当引擎返回了大量中间行时
- 索引覆盖(
Using index)可避免回表,但不影响 Server 层是否还需过滤
SELECT 列列表直接影响引擎读取行为
执行器传给引擎的不只是 WHERE 条件,还有明确的列投影需求。这决定了 InnoDB 是否必须读溢出页(off-page)。
比如表含 TEXT 字段,SELECT * 会让引擎强制加载所有溢出页;而 SELECT id, name(且这两列都在主键页内)则跳过溢出页,只读聚簇索引页内的紧凑记录。
- 二级索引覆盖查询(
Using index)时,引擎只读索引页,完全不访问聚簇索引页 - 哪怕只查一列,只要该列在溢出页中(如大
BLOB),引擎仍需读溢出页 - 执行器不会主动“裁剪”行数据——裁剪逻辑由引擎根据列请求决定
流式读取依赖客户端驱动的 fetch size 设置
所谓“一行行读取”,实际发生在客户端与 MySQL 服务端之间的网络传输层,而非执行器内部。执行器持续调用 ha_next(),但服务端是否把结果分批发回,取决于客户端 JDBC 的 fetchSize 或 Python MySQL Connector 的 cursor.execute(..., buffered=False)。
- 默认是 buffer 模式:服务端一次性生成全部结果集并缓存,再整块发给客户端
- 设为流式(unbuffered)后,服务端边执行边发包,客户端才能真正“逐行接收”
- 流式模式下,连接不能复用(因结果集未取完时不能执行新语句),且事务中需注意锁持有时间延长
SELECT 列和 WHERE 表达式,直接决定了引擎是否读溢出页、是否触发 Server 层二次计算、以及最终有多少字节经网络到达客户端。











