字节真实面经:以Mysql为例,讲一下一条SQL的执行过程和原理!

🕵‍♂ 一条 SQL 的奇幻漂流:从敲下回车到结果返回,MySQL 到底经历了什么?

"面试官问我:'你知道一条 SQL 在数据库里是怎么执行的吗?'

我当时一愣,心想:不就是执行嘛,还能咋执行?结果......被问懵了。"

这恐怕是很多后端同学第一次被"SQL 执行流程"拷问灵魂的瞬间 😂。

明明平时写 SQL 写得飞起,什么 SELECT * FROM user WHERE id = 1; 手到擒来,

可真让你说清楚它从 「回车键」「结果返回」 的全过程,

十有八九都是一脸懵圈。

今天我们就来当一回数据库侦探 🕵‍♂,

跟着这条 SQL 一起,深入 MySQL 的地底世界。

顺便也看看它的老对手 PostgreSQL 是怎么干这事的。


🌊 一条 SQL 的旅程,从客户端到结果集

假设我们执行这样一句 SQL:

sql 复制代码
SELECT name, age FROM users WHERE city = 'Shanghai' ORDER BY age DESC LIMIT 10;

这句话从敲下回车的那一刻起,其实会经历 「七个阶段」 👇

「连接 → 查询缓存 → 解析器 → 优化器 → 执行器 → 存储引擎 → 返回结果」

别急,我们一个一个扒开看。


🧩 1. 连接管理器(Connection Manager)

MySQL 是个典型的 「客户端/服务器架构」 。 当你通过客户端(比如 Navicat、Go 的 database/sql)发起连接时, MySQL 会先进入 「连接管理阶段」

「MySQL」 使用线程池模型,一个连接对应一个线程。 ✅ 「PostgreSQL」 传统上是一个连接一个进程(也因此多连接下开销更大)。

连接建立好后,MySQL 会先验证账号密码、权限等。 连接成功后,你就能愉快地发送 SQL 了。

👉 小知识点: 「MySQL 8.0 引入了连接池 plugin,可以减少线程开销。」 而 PostgreSQL 则常用第三方连接池(如 pgBouncer)来减压。


💡 2. 查询缓存(Query Cache)------曾经的"短命天才"

MySQL 曾经有一个 「Query Cache」, 如果你发的 SQL 在缓存里有一模一样的结果,它直接返回 ✅。

问题是,只要表中有一行数据被更新, 相关缓存就全部失效 ❌。 高并发场景下,这反而拖慢了性能。

于是从 MySQL 8.0 开始,「Query Cache 被彻底移除」 。 而 PostgreSQL 则一直没有内建 Query Cache, 因为它更倾向于用 「共享缓冲区 + 执行计划缓存」 来提速。


🔍 3. 解析器(Parser)------把 SQL 拆成语法树 🌳

解析器的工作就像语文老师批作文。 它要判断这句话语法对不对, 然后把 SQL 拆成一个内部可理解的结构:「抽象语法树(AST)」

举个例子 👇 我们的 SQL:

sql 复制代码
SELECT name, age FROM users WHERE city = 'Shanghai' ORDER BY age DESC LIMIT 10;

会被解析成一棵树结构(简化版):

sql 复制代码
Select
 ├── Columns: [name, age]
 ├── From: users
 ├── Where: city = 'Shanghai'
 ├── OrderBy: age DESC
 └── Limit: 10

PostgreSQL 同理,也会生成 AST,但它在此阶段就做了更多语义检查(比如字段是否存在)。


🧠 4. 优化器(Optimizer)------SQL 的灵魂工程师

到这里是重头戏 🎯。 优化器的任务是:

"在一堆可能的执行方案中,选出**「代价最小」**的那一个。"

比如上面的查询,优化器要考虑:

  • 要不要用索引?哪个索引?
  • 是先过滤 city 还是先排序?
  • 是否可以走覆盖索引?
  • 是否能用回表、临时表、文件排序?

MySQL 使用 「基于代价的优化器(Cost-Based Optimizer)」。 它会估算每种执行计划的代价(I/O、CPU、行数等),然后选最优。

而 PostgreSQL 则以优化器强悍著称: 它支持更多的 join 策略(如 Hash Join、Merge Join) 还能利用统计信息动态决定 plan。

🧩 小技巧: 执行前可以通过 EXPLAIN 看执行计划。 比如:

sql 复制代码
EXPLAIN SELECT name, age FROM users WHERE city = 'Shanghai' ORDER BY age DESC LIMIT 10;

这会告诉你是否使用了索引、扫描方式(全表/索引)、排序代价等。

👉 「那怎么判断是否使用了索引?」

执行结果一般会有一列叫 type 和一列叫 key

字段 含义
type 表示访问类型(性能从好到坏:system > const > eq_ref > ref > range > index > ALL)
key 表示使用的索引名,如果是 NULL 则说明没用索引

✅ 例子一:

go 复制代码
type: range, key: idx_city

说明走了索引扫描(利用了 city 索引)

❌ 例子二:

yaml 复制代码
type: ALL, key: NULL

说明是全表扫描(MySQL 从头到尾扫一遍表的数据页)

一般来说,全表扫描 (ALL) 性能最差,尤其是大表上。 而索引扫描(range / ref)表示查询条件命中索引,效率更高。 可以用 EXPLAIN ANALYZE(MySQL 8.0.18+)进一步看到真实执行时间。


⚙️ 5. 执行器(Executor)------执行计划落地的地方

优化器决定了"怎么干",执行器就负责"真干"。 它会按照优化器的 plan,调用底层的存储引擎 API。

比如说它要从表里取数据,就会调用:

lua 复制代码
engine.read("users", condition)

(当然底层不是真的 Go 哈,这只是方便理解 😆)

每读一行,就判断 city == 'Shanghai', 符合条件的就放进结果集,最后排序、截取前 10 条。


🗄️ 6. 存储引擎层(Storage Engine Layer)

这是 MySQL 最有"灵魂分裂感"的部分 😂。 因为不同表,可以有不同的存储引擎!

最常见的当然是 「InnoDB」

InnoDB 负责的数据层逻辑包括:

  • 数据页缓存(Buffer Pool)
  • 索引(B+ Tree)
  • 事务(Redo/Undo 日志)
  • 行锁机制

而 PostgreSQL 没有"存储引擎"这个概念, 它的存储层和执行层是紧耦合的。 但它在 「MVCC(多版本并发控制)」 上比 MySQL 更纯粹:每次更新会产生新版本,而不是回滚段。


📤 7. 返回结果(Result Set)

当执行器拿到结果集后, 会通过网络协议一批一批地返回给客户端。

你以为这一步就结束了? 其实不是 😅

客户端驱动(比如 Go 的 sql.Rows) 还会逐行读取结果、反序列化成结构体。 这就是为什么一次性查询 10 万条时,客户端内存会爆炸的原因 🚀。


🧭 总结:MySQL 执行 SQL 的完整图示

下面这张图就是整条 SQL 的生命周期:

css 复制代码
[客户端]
   │
   ▼
[连接器] → [查询缓存(已废弃)] → [解析器] → [优化器] → [执行器] → [存储引擎]
   │                                                    │
   └────────────────────────────── 结果集返回 ───────────┘

💬 延伸思考

  1. PostgreSQL 的优化器比 MySQL 强在哪里? 👉 更智能的执行计划评估 + 更丰富的 join 策略。
  2. 为什么深分页 (LIMIT 1000000, 10) 慢? 👉 因为执行器仍要扫描前一百万行,只是丢掉不用的部分。 (这也是上一篇我们讲过的优化点 😎)
  3. 为什么有时候 EXPLAIN 看似走了索引却仍然慢? 👉 因为数据分布、索引选择、排序代价都可能抵消索引优势。

💬 小结一下(怕你看累了 ☕)

阶段 关键职责 对应 PostgreSQL 对比
连接器 线程管理、权限校验 进程模型,性能更稳
查询缓存 8.0 已废弃 无,靠 plan cache
解析器 生成语法树 同理,但语义更严
优化器 选最优执行计划 更智能的代价模型
执行器 调用引擎执行 更统一的执行框架
存储引擎 InnoDB 事务、索引、锁 内置 MVCC,无引擎层
返回结果 网络传输 同理

🎯 最后的思考题

如果面试官问你: "一条 SQL 在 MySQL 里是怎么执行的?" 你是不是能像讲故事一样,把上面这些层级自然串起来?😉

记住,懂执行顺序不仅是为了背面试题, 更是为了当系统慢时,能知道该"查哪一层"。

比如:

  • 慢在优化器:可能统计信息不准;
  • 慢在执行器:可能索引没生效;
  • 慢在存储层:可能 I/O 或锁冲突;
  • 慢在客户端:可能网络或反序列化太重。

只有理解了整个流程,才能真正玩转数据库。 别忘了,「SQL 不只是数据语言,它是系统协作的艺术。」


📚 如果你看到这里觉得有收获,点个「在看」或者转发给你那位被 SQL 折磨的朋友吧 😆 下一篇,我们聊聊------为什么 MySQL 的 LIMIT 深分页那么慢,以及几种实际优化方案 🚀。


「技术有道|用故事讲技术,让底层原理不再枯燥」 VX搜索Tech有道 , 获取更多资料和真实面经

相关推荐
咕白m6254 小时前
通过 C# 给Word文档添加水印:文字水印、图片水印
后端·c#
壹米饭4 小时前
QuestDB数据不能新增问题解决方案
数据库·后端
MaxHua4 小时前
JAVA开发处理金额的数据类型你知道多少?
java·后端
Undoom4 小时前
AIGC-Fooocus部署实践:从本地手动配置到云端一键启用的深度剖析
后端
LCG元4 小时前
Docker容器化实战:将你的SpringBoot应用一键打包部署(三)-配置告警和自动扩缩容
后端·docker
知其然亦知其所以然4 小时前
我被问懵了:Tomcat 到底有几种部署方式?
后端·面试·tomcat
一个喜欢分享的PHP技术4 小时前
使用JdbcTemplate访问MySQL数据库
数据库
用户68545375977694 小时前
Spring WebFlux响应式编程的奇幻漂流 🌊
后端
BingoGo4 小时前
PHP 异常处理全攻略 Try-Catch 从入门到精通完全指南
后端·php