执行引擎概述

执行引擎(Executor) 负责调用存储引擎接口,执行SQL,返回结果。

SQL执行完整流程:
┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐
│ 连接器     │→│ 分析器     │→│ 优化器     │→│ 执行器     │
└──────────┘  └──────────┘  └──────────┘  └──────────┘
                                             ↓ 调用接口
                                        ┌──────────┐
                                        │ 存储引擎  │
                                        └──────────┘

执行引擎的职责

1. 权限检查

-- 查询前检查权限
SELECT * FROM users WHERE id = 1;

-- 检查流程
1. 当前用户是否有users表的SELECT权限
2. 如果没有  返回错误:Access denied

-- 权限存储位置
mysql.user        -- 全局权限
mysql.db          -- 数据库权限
mysql.tables_priv -- 表权限

2. 调用存储引擎接口

-- 示例SQL
SELECT * FROM users WHERE id = 1;

-- 执行引擎操作
1. 打开表:handler = open_table("users")
2. 初始化查询:handler->index_init(id索引)
3. 定位第一行:handler->index_read(id=1)
4. 读取行数据:handler->read_row()
5. 关闭表:handler->close()

3. 结果返回

-- 返回流程
1. 存储引擎返回数据行
2. 执行引擎应用WHERE条件过滤
3. 格式化结果(MySQL协议
4. 通过网络发送给客户端

-- 流式返回(Streaming)
-- MySQL边查询边返回,不等全部数据查完

存储引擎接口(Handler API)

1. 打开表

// C代码示例(简化)
int handler::ha_open(TABLE *table, const char *name, int mode);

// 流程
1. 打开表文件(.ibd
2. 加载表结构(.frmMySQL 8.0已废弃)
3. 初始化Handler对象

2. 读取接口

// 全表扫描
int handler::rnd_init();         // 初始化全表扫描
int handler::rnd_next(uchar *buf); // 读取下一行

// 索引扫描
int handler::index_init(uint idx);           // 初始化索引扫描
int handler::index_read(uchar *buf, const uchar *key, uint key_len);  // 定位第一行
int handler::index_next(uchar *buf);         // 读取下一行

3. 写入接口

// 插入
int handler::write_row(uchar *buf);

// 更新
int handler::update_row(const uchar *old_data, const uchar *new_data);

// 删除
int handler::delete_row(const uchar *buf);

执行流程详解

1. 全表扫描

-- SQL
SELECT * FROM users WHERE age > 25;

-- 执行流程
1. 打开表:open_table("users")
2. 初始化全表扫描:rnd_init()
3. 循环读取
   WHILE (rnd_next(row) == 0) DO
       IF age > 25 THEN
           发送到客户端
       END IF
   END WHILE
4. 关闭表:close()

-- 扫描行数:全表所有行

2. 索引扫描

-- SQL
SELECT * FROM users WHERE id = 1;

-- 执行流程(主键索引)
1. 打开表:open_table("users")
2. 初始化索引扫描:index_init(PRIMARY)
3. 定位第一行:index_read(id=1)
4. 读取行数据:read_row()
5. 发送到客户端
6. 关闭表:close()

-- 扫描行数:1行(主键等值查询)

3. 索引范围扫描

-- SQL
SELECT * FROM users WHERE id BETWEEN 1 AND 100;

-- 执行流程
1. 打开表:open_table("users")
2. 初始化索引扫描:index_init(PRIMARY)
3. 定位第一行:index_read(id=1)
4. 循环读取
   WHILE (index_next(row) == 0 AND id <= 100) DO
       发送到客户端
   END WHILE
5. 关闭表:close()

-- 扫描行数:100行(range查询)

JOIN执行流程

Nested Loop Join

-- SQL
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id;

-- 执行流程(Nested Loop Join)
1. 打开orders表open_table("orders")
2. 打开users表open_table("users")
3. 初始化orders全表扫描orders->rnd_init()
4. 循环orders表
   FOR EACH order IN orders DO
       -- 内层:通过索引查找users表
       users->index_read(id = order.user_id)
       users->read_row()
       -- 拼接结果,发送客户端
   END FOR
5. 关闭表

-- 扫描次数
orders表:全表扫描(M行
users表:索引查找(M次,每次O(log N)
总复杂度:O(M × log N)

执行器优化

1. 条件下推

-- SQL
SELECT * FROM users WHERE id > 100;

-- 未优化
1. 存储引擎返回所有行
2. 执行器过滤 id > 100
3. 返回结果

-- ICP优化(Index Condition Pushdown)
1. 存储引擎扫描索引时,直接过滤 id > 100
2. 返回满足条件的行
3. 减少回表次数

2. 批量读取

-- Multi-Range Read(MRR)优化
-- 场景:范围查询 + 回表

-- 未优化
1. 扫描索引:获取10个主键ID(随机顺序)
2. 逐个回表:10次随机IO

-- MRR优化
1. 扫描索引:获取10个主键ID
2. 排序主键ID(变为顺序)
3. 批量回表:1次顺序IO

执行器与优化器的配合

-- SQL
SELECT * FROM users WHERE name = 'Alice' AND age = 25;

-- 优化器决策
1. 选择索引:idx_name_age
2. 生成执行计划:
   - type: ref
   - key: idx_name_age
   - rows: 1

-- 执行器执行
1. 调用存储引擎接口:index_read(idx_name_age, 'Alice', 25)
2. 存储引擎返回数据行
3. 执行器返回客户端

查看执行器操作

1. SHOW PROFILE

-- 开启profiling
SET profiling = 1;

-- 执行SQL
SELECT * FROM users WHERE id = 1;

-- 查看执行耗时
SHOW PROFILES;

-- 详细分析
SHOW PROFILE FOR QUERY 1;

-- 输出示例
+----------------------+----------+
| Status               | Duration |
+----------------------+----------+
| starting             | 0.000050 |
| checking permissions | 0.000005 |
| Opening tables       | 0.000020 |
| init                 | 0.000010 |
| System lock          | 0.000005 |
| optimizing           | 0.000005 |
| statistics           | 0.000010 |
| preparing            | 0.000010 |
| executing            | 0.000005 |   执行器调用存储引擎
| Sending data         | 0.000050 |   存储引擎返回数据
| end                  | 0.000005 |
| query end            | 0.000005 |
| closing tables       | 0.000005 |
| freeing items        | 0.000010 |
| cleaning up          | 0.000010 |
+----------------------+----------+

2. Performance Schema

-- 查看执行引擎调用存储引擎的次数
SELECT * FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT LIKE 'SELECT%users%'
LIMIT 1\G

-- 关键指标
COUNT_STAR         : 10000   -- 执行次数
SUM_ROWS_EXAMINED  : 100000  -- 扫描行数
SUM_ROWS_SENT      : 10000   -- 返回行数

执行器性能影响

1. 扫描行数

-- 全表扫描(扫描100万行)
SELECT * FROM users;
-- 执行器调用100万次 rnd_next()

-- 索引查询(扫描1行)
SELECT * FROM users WHERE id = 1;
-- 执行器调用1次 index_read()

-- 优化:减少扫描行数

2. 回表次数

-- 非覆盖索引(需要回表)
SELECT * FROM users WHERE name = 'Alice';
-- 1. 索引扫描:index_read(name='Alice')
-- 2. 回表:read_row(主键=1)

-- 覆盖索引(无需回表)
SELECT id, name FROM users WHERE name = 'Alice';
-- 1. 索引扫描:index_read(name='Alice')
-- 2. 直接返回(无需回表)

-- 优化:使用覆盖索引

实战建议

1. 减少扫描行数

-- ❌ 不好:全表扫描
SELECT * FROM users WHERE age > 25;

-- ✅ 好:使用索引
CREATE INDEX idx_age ON users(age);
SELECT * FROM users WHERE age > 25;

2. 使用覆盖索引

-- ❌ 不好:需要回表
SELECT * FROM users WHERE name = 'Alice';

-- ✅ 好:覆盖索引
CREATE INDEX idx_name_age ON users(name, age);
SELECT name, age FROM users WHERE name = 'Alice';

3. 避免大结果集

-- ❌ 不好:返回100万行
SELECT * FROM users;

-- ✅ 好:分页查询
SELECT * FROM users LIMIT 0, 100;

常见面试题

Q1: 执行引擎的作用是什么?

  • 调用存储引擎接口,执行SQL,返回结果

Q2: 执行引擎如何调用存储引擎?

  • 通过Handler API(rnd_next、index_read等接口)

Q3: 如何查看SQL执行过程?

  • SHOW PROFILEPerformance Schema

Q4: 如何优化执行器性能?

  • 减少扫描行数、使用覆盖索引、避免大结果集

小结

执行引擎:调用存储引擎接口,执行SQL ✅ Handler API:rnd_next、index_read、write_row等接口 ✅ 执行流程:打开表 → 扫描数据 → 应用条件 → 返回结果 ✅ 优化建议:减少扫描行数、使用覆盖索引

执行引擎是SQL执行的最后一步。


📚 相关阅读

  • 下一篇:《InnoDB架构综合实战》
  • 推荐:《查询优化器:生成最优执行计划》