执行引擎:SQL执行的最后一步

执行引擎概述 执行引擎(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. 加载表结构(.frm,MySQL 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: 执行引擎的作用是什么? ...

2025-01-15 · maneng

如约数科科技工作室

浙ICP备2025203501号

👀 本站总访问量 ...| 👤 访客数 ...| 📅 今日访问 ...