SQL基础:SELECT执行流程
大家好,我是一名拥有10年以上经验的DBA老兵。
做这个系列,源于一个朴素的愿望:把踩过的坑、总结的经验系统化输出,希望能帮到刚入行或想进阶的兄弟们。
让我们开始今天的第8天内容。
🎯 面试考点
- SELECT 语句从输入到输出经历了哪些步骤?
- 查询缓存为什么在 MySQL 8.0 被移除了?
- 优化器是怎么选择索引的?
- 执行器是怎么获取数据的?
背景引入
💡 说白了:你以为 SELECT 只是查一下?其实它跑了半个数据库
你有没有想过:
- 你敲下
SELECT * FROM user WHERE id = 1,MySQL 到底做了什么? - 为什么有时候明明加了索引,MySQL 却不用?
- 为什么同样的 SQL,有时候快有时候慢?
说实话,不理解 SELECT 执行流程的 DBA,就像不知道汽车怎么跑的司机——能开,但出了问题只能干瞪眼。
今天的目标:搞懂 SELECT 语句的完整执行流程,面试必问。
核心概念
执行流程全景图

第一步:连接器
💡 说白了:连接器就是门卫,验明正身才让进
职责:
- 建立 TCP 连接
- 验证用户名密码
- 获取用户权限(后续操作都依赖这个权限)
面试必问:
- 为什么有时候连接很慢?
- 长连接和短连接有什么区别?
- 为什么长连接会导致内存泄漏?
面试解答:
Q: 为什么有时候连接很慢?
因为建立 TCP 连接需要三次握手,如果数据库服务器距离远或者网络差,连接就会慢。建议使用连接池,复用连接。
Q: 长连接和短连接有什么区别?
短连接:每次执行完 SQL 就断开连接。长连接:执行完 SQL 后保持连接,下次复用。长连接减少了建立连接的开销,但会导致内存增长。
第二步:查询缓存(MySQL 8.0 已移除)
💡 说白了:查询缓存就是"抄作业",SQL 完全一样就直接返回结果
工作机制:
- 检查 SQL 是否命中缓存(精确匹配,包括空格)
- 命中 → 直接返回结果
- 未命中 → 继续执行后续步骤,结果放入缓存
为什么 MySQL 8.0 移除了?
- 缓存失效太频繁:表有任何更新,该表所有缓存失效
- 命中率低:业务 SQL 通常有参数差异
- 维护成本高:需要额外的锁机制
-- MySQL 5.7 可以手动关闭查询缓存
SET GLOBAL query_cache_type = 0;
-- 查看缓存状态
SHOW STATUS LIKE 'Qcache%';
第三步:解析器
💡 说白了:解析器就是"语文老师",检查 SQL 语法对不对
职责:
- 词法分析:识别关键字、表名、列名
- 语法分析:检查 SQL 语法是否正确
-- 语法错误示例
SELEC * FROM user; -- 报错:SELEC 不是有效关键字
-- 语义错误示例(解析器检查不出,在预处理器报错)
SELECT * FROM user WHERE non_exist_column = 1; -- 如果列不存在,在预处理器报错
第四步:预处理器
💡 说白了:预处理器就是"班主任",检查表和列是否存在
职责:
- 检查表名、列名是否存在
- 检查用户是否有权限访问这些表和列
- 展开
SELECT *为具体列名
-- 预处理器会检查
SELECT * FROM user WHERE age > 18;
-- 等价于
SELECT id, name, age, ... FROM user WHERE age > 18;
第五步:优化器
💡 说白了:优化器就是"军师",决定怎么执行最高效
职责:
- 选择使用哪个索引
- 决定表的连接顺序
- 选择最优的执行计划
优化器的工作原理:
-- 优化器会考虑以下因素
EXPLAIN SELECT * FROM user WHERE age > 18 AND city = '北京';
-- 优化器可能选择:
-- 方案1:使用 idx_age 索引,然后回表过滤 city
-- 方案2:使用 idx_city 索引,然后回表过滤 age
-- 方案3:使用联合索引 idx_age_city(如果存在)
面试必问:
- 优化器是基于什么选择索引的?
- 优化器选择错了怎么办?
面试解答:
Q: 优化器是基于什么选择索引的?
基于统计信息(cardinality、数据分布等)估算成本,选择成本最低的执行计划。但统计信息可能不准确,导致优化器选错索引。
Q: 优化器选择错了怎么办?
可以使用
FORCE INDEX强制指定索引,或者ANALYZE TABLE更新统计信息。
第六步:执行器
💡 说白了:执行器就是"干活的人",真正去存储引擎取数据
职责:
- 根据执行计划,调用存储引擎的接口获取数据
- 对结果进行过滤、排序、聚合等处理
-- 执行器的工作流程
SELECT * FROM user WHERE age > 18;
-- 1. 调用存储引擎的索引接口,获取满足条件的记录ID
-- 2. 调用存储引擎的行读取接口,获取完整行数据
-- 3. 返回结果集给客户端
实战案例
场景一:查看 SELECT 执行流程
-- 开启性能监控
SET profiling = 1;
-- 执行查询
SELECT * FROM user WHERE id = 1;
-- 查看执行过程
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;
输出示例:
+----------------------+----------+
| Status | Duration |
+----------------------+----------+
| starting | 0.000045 |
| checking permissions | 0.000006 |
| Opening tables | 0.000015 |
| init | 0.000008 |
| System lock | 0.000006 |
| optimizing | 0.000004 |
| statistics | 0.000012 |
| preparing | 0.000008 |
| executing | 0.000003 |
| Sending data | 0.000035 |
| end | 0.000003 |
| query end | 0.000002 |
| closing tables | 0.000004 |
| freeing items | 0.000015 |
| cleaning up | 0.000003 |
+----------------------+----------+
场景二:优化器选错索引
-- 创建测试表
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
status VARCHAR(20),
amount DECIMAL(10,2),
create_time DATETIME,
INDEX idx_user_id (user_id),
INDEX idx_status (status),
INDEX idx_create_time (create_time)
);
-- 插入数据(status 分布不均匀)
INSERT INTO orders (user_id, status, amount, create_time)
SELECT
FLOOR(RAND() * 10000),
CASE WHEN RAND() < 0.9 THEN 'completed' ELSE 'pending' END,
ROUND(RAND() * 1000, 2),
DATE_ADD('2026-01-01', INTERVAL FLOOR(RAND() * 365) DAY)
FROM (
WITH RECURSIVE cte AS (SELECT 1 AS n UNION ALL SELECT n+1 FROM cte WHERE n<100000)
SELECT * FROM cte
) tmp;
-- 查看执行计划
EXPLAIN SELECT * FROM orders WHERE status = 'pending' AND create_time > '2026-06-01';
-- 优化器可能选错索引,强制指定
SELECT * FROM orders FORCE INDEX(idx_create_time) WHERE status = 'pending' AND create_time > '2026-06-01';
💡 AI 辅助实战
🤖 AI 能不能帮我对比不同 SQL 写法的执行流程?
能!让 AI 分析同一需求的不同写法,对比执行流程和性能差异:
提示词示例:
"以下两种写法都能查询有订单的用户,请分析执行流程有什么区别:
写法1(子查询):
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders)
写法2(JOIN):
SELECT DISTINCT u.* FROM users u JOIN orders o ON u.id = o.user_id
请分析:
1. 两种写法的执行流程
2. 哪种写法性能更好
3. 什么场景下选择哪种写法"
AI 分析结果:
| 对比项 | 写法1(子查询) | 写法2(JOIN) |
|---|---|---|
| 执行流程 | 先执行子查询,再用 IN 匹配 | 先 JOIN,再去重 |
| 索引使用 | 可能无法使用索引 | 可以使用索引 |
| 临时表 | 可能需要临时表 | 需要临时表去重 |
| 性能 | 数据量大时较慢 | 通常更快 |
AI 优化建议:
- 写法1 改成 EXISTS 或 JOIN
- 写法2 可以用 GROUP BY 代替 DISTINCT
- 都可以加索引优化
避坑指南
⚠️ 真实踩过的坑:
不要迷信 EXPLAIN
- EXPLAIN 只是优化器的预估,一般不会有太大偏差,但它不是实际执行情况
- 建议:用
EXPLAIN ANALYZE(MySQL 8.0+)看真实执行
长连接要定期重置
- 长连接会导致内存增长(临时表、排序缓冲区等)
- 建议:定期调用
mysql_reset_connection()重置会话状态
C API 示例:
MYSQL *conn = mysql_init(NULL); mysql_real_connect(conn, "localhost", "root", "", "test", 0, NULL, 0); mysql_query(conn, "SELECT * FROM user"); // 重置连接(不断开,只清理会话状态) mysql_reset_connection(conn);优化器统计信息要定期更新
- 统计信息过期会导致优化器选错索引
- 建议:定期执行
ANALYZE TABLE
思考题
🤔 互动时间:
- 如果 SELECT 语句执行很慢,你会按什么顺序排查?
- 为什么 MySQL 8.0 移除了查询缓存?
- 优化器选错了索引,除了
FORCE INDEX,还有什么办法?
总结
🎯 面试考点
- 执行流程:连接器 → 查询缓存 → 解析器 → 预处理器 → 优化器 → 执行器
- 连接器:权限验证、长连接/短连接
- 查询缓存:MySQL 8.0 已移除,因为失效频繁、命中率低
- 解析器:词法分析、语法分析
- 优化器:基于统计信息选择最优执行计划
- 执行器:调用存储引擎获取数据
下期预告:WHERE子句优化技巧 —— 面试必问!
您的关注是我每日更新的动力:没有那多
有问题欢迎评论区交流,明天见!