执行器不直接调用存储引擎,所有读取均经handler接口层中转;ha_index_read()在走索引时触发(find_flag控制语义),ha_rnd_next()仅在Type:ALL且无覆盖索引时调用,需ha_rnd_init()成功;“Using index”时跳过ha_index_read()直取索引页数据,InnoDB支持而MyISAM不支持;external_lock()高频调用属正常握手流程。

执行器不直接调用存储引擎,所有数据读取都必须经过 handler 接口层中转;理解它,关键不是看 SQL 怎么写,而是看执行器在什么条件下调哪个 handler 函数、参数怎么传、返回值怎么解释。
ha_index_read() 和 ha_rnd_next() 到底什么时候被触发
这两个函数是执行器访问数据最常走的两条路径,但触发条件完全不同:
-
ha_index_read()在优化器决定走某个索引(哪怕只是排序或 GROUP BY 的辅助索引)时调用,find_flag参数决定语义:HA_READ_KEY_EXACT对应=,HA_READ_KEY_OR_NEXT对应>=,HA_READ_AFTER_KEY对应> -
ha_rnd_next()只有当执行计划显示Type: ALL(即全表扫描),且没命中覆盖索引时才走;它依赖前置的ha_rnd_init()成功初始化游标,否则会直接返回HA_ERR_END_OF_FILE即使表里有数据 - 注意:如果事务隔离级别是
REPEATABLE READ且快照为空,ha_rnd_next()也可能返回空——不是没数据,而是当前事务看不见
“Using index” 为什么没进 ha_index_read()
EXPLAIN 显示 Using index 意味着优化器选了覆盖索引,执行器会跳过 handler 层的完整行构造流程,直接从索引页内存结构里用 key_copy() 提取字段值。这带来两个后果:
- InnoDB 支持该优化,MyISAM 不支持——所以同样 SQL 在两种引擎下 handler 调用路径可能完全不同
- 只要
SELECT中出现一个非索引字段(比如c是TEXT且不在联合索引里),就会强制回表,触发ha_index_read()去聚簇索引查完整记录 - 调试时若想绕过这个优化、确保走到 handler,可加
FORCE INDEX并故意选一个不覆盖全部 SELECT 字段的索引
external_lock() 被反复调用却没报错,正常吗
完全正常。external_lock() 不是加锁操作本身,而是执行器向引擎“打招呼”:我准备开始访问这张表了。它的作用很轻量:
- 每次 SQL 执行前,执行器调一次
external_lock(LOCK_TABLE_READ)或LOCK_TABLE_WRITE - SQL 执行完(无论成功失败),再调一次
external_lock(UNLOCK_TABLE) - InnoDB 用它来绑定当前线程的
prebuilt->trx和锁等待队列;MyISAM 则基本只做标记,不涉及事务上下文 - 所以你在 perf 或 gdb 里看到它高频出现,不代表有锁冲突,只是执行器的标准握手流程
调试 handler 调用时最容易忽略的一点
断点打在基类 handler::index_read() 上大概率不会命中——因为实际调用的是派生类实现,比如 ha_innobase::index_read()。你得确认符号表加载完整,并在具体引擎子类里下断点;否则看到的 ha_index_read() 调用栈,只是虚函数分发的中间跳转,真正干活的代码还在下面两层。


















