执行器调用ha_innobase::index_read()或ha_rnd_next()时,innodb在buffer pool中查找数据页,未命中则同步加载整页(16kb),再在页内二分定位记录;b+树导航需逐层加载非叶子页,二级索引还需回表;全表扫描按物理页顺序遍历,加锁与mvcc可见性判断均由innodb内部完成。

执行器调用 ha_innobase::index_read() 或 ha_innobase::rnd_next() 时发生了什么
执行器不直接读磁盘,它只向存储引擎发出「按主键查一行」或「顺序取下一行」的指令。InnoDB 收到后,会立刻进入 Buffer Pool 查找对应的数据页——不是查单行,而是查整页(16KB)。如果该页不在内存中,就触发一次同步 I/O,从 .ibd 文件加载整页进 Buffer Pool,再在页内用二分法定位具体记录。这个过程对执行器是透明的,它只看到「返回了一行」。
为什么 WHERE id = 10 不等于「只读一个数据页」
InnoDB 的 B+ 树导航是逐层下降的:先查根页 → 找到子页指针 → 加载子页 → 再查,直到叶子节点页。即使最终只用到叶子页里的一行,中间非叶子页(如根页、内部节点页)也必须被加载进 Buffer Pool 才能完成导航。这些页可能早已缓存,也可能需要额外 I/O。尤其当表很大、B+ 树深度 ≥ 4 时,一次点查可能触发 3~4 次页加载(含非叶子页)。
- 根页通常常驻 Buffer Pool,但高并发下也可能被淘汰
- 二级索引查找会多一次「回表」:先查二级索引页 → 得到主键值 → 再按主键查聚簇索引页
-
SELECT *和SELECT id在页加载量上没区别,区别只在后续是否需要回表
ha_innobase::rnd_next() 迭代全表扫描时的页访问模式
全表扫描不走索引树导航,而是按数据页物理顺序遍历。InnoDB 会从第一个叶子页开始,依次加载、扫描每一页里的所有记录。但注意:页内记录顺序 ≠ 主键插入顺序(可能因页分裂、删除造成空洞),且 Buffer Pool 会按 LRU 管理这些页——刚扫过的页未必留在内存里,下次迭代可能又触发磁盘 I/O。
- 如果表有
ORDER BY id但无可用索引,MySQL 会在执行器层做文件排序(Using filesort),此时 InnoDB 仍只是顺序吐行,排序由 Server 层完成 - 全表扫描期间,Change Buffer 不生效(它只针对 DML 的二级索引更新)
- 大表扫描易触发 Buffer Pool 淘汰,Old Sublist 中的老页会被优先踢出,影响其他查询命中率
迭代过程中加锁和可见性判断发生在哪一层
行锁(Record Lock)、间隙锁(Gap Lock)、临键锁(Next-Key Lock)全部由 InnoDB 在数据页扫描/定位时实时加,不是执行器决定的。而 MVCC 可见性判断(比如 RR 隔离级别下是否能看到某版本)也完全在 InnoDB 内部完成:它检查当前事务的 trx_id 和每行记录的 DB_TRX_ID、DB_ROLL_PTR,结合 undo log 判断是否可读。执行器拿到的,已经是过滤完不可见版本后的「干净行」。
- 即使是
SELECT ... FOR UPDATE,加锁动作也发生在 InnoDB 定位到行之后、返回给执行器之前 - 执行器不会对「已返回的行」再次校验权限或可见性——它信任存储引擎返回的结果
- 如果某行被其他事务锁住,
index_read()会阻塞,直到锁释放或超时(innodb_lock_wait_timeout)
next() 调用,InnoDB 内部可能已完成导航、加载、解析、加锁、可见性判断、版本回溯五步动作。











