一条 SQL 语句在 MySQL 中的执行过程详解

1. 引言

很多同学在面试或日常开发中都会遇到这样一个问题:一条 SQL 语句从客户端发出,到最终返回结果,MySQL 内部到底经历了什么?

理解这条链路,不仅能帮你在面试中答得清楚,更能让你在排查慢查询、锁等待、连接数打满等问题时,快速定位到是哪个环节出了问题。

本文以一条最常见的查询语句为例,完整梳理它在 MySQL 中的执行过程:

sql 复制代码
SELECT id, name, age FROM user WHERE id = 1;

同时也会简单对比更新语句(UPDATE)的差异,因为更新涉及 redo log、undo log、binlog 的协同,是理解 MySQL 事务与持久化的关键。

2. 整体架构:Server 层与存储引擎层

MySQL 的逻辑架构大致可以分为两层:

  • Server 层:负责连接管理、SQL 解析、优化、执行等,是跨存储引擎的通用层,包含连接器、查询缓存(8.0 已移除)、分析器、优化器、执行器。
  • 存储引擎层:负责数据的实际存取,插件式设计,常见的有 InnoDB、MyISAM、Memory 等,其中 InnoDB 是 5.5 之后的默认引擎。

整体流程可以用下图概括:
#mermaid-svg-eGc1UdbMCQcT5HWX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eGc1UdbMCQcT5HWX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eGc1UdbMCQcT5HWX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eGc1UdbMCQcT5HWX .error-icon{fill:#552222;}#mermaid-svg-eGc1UdbMCQcT5HWX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eGc1UdbMCQcT5HWX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eGc1UdbMCQcT5HWX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eGc1UdbMCQcT5HWX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eGc1UdbMCQcT5HWX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eGc1UdbMCQcT5HWX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eGc1UdbMCQcT5HWX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eGc1UdbMCQcT5HWX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eGc1UdbMCQcT5HWX .marker.cross{stroke:#333333;}#mermaid-svg-eGc1UdbMCQcT5HWX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eGc1UdbMCQcT5HWX p{margin:0;}#mermaid-svg-eGc1UdbMCQcT5HWX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eGc1UdbMCQcT5HWX .cluster-label text{fill:#333;}#mermaid-svg-eGc1UdbMCQcT5HWX .cluster-label span{color:#333;}#mermaid-svg-eGc1UdbMCQcT5HWX .cluster-label span p{background-color:transparent;}#mermaid-svg-eGc1UdbMCQcT5HWX .label text,#mermaid-svg-eGc1UdbMCQcT5HWX span{fill:#333;color:#333;}#mermaid-svg-eGc1UdbMCQcT5HWX .node rect,#mermaid-svg-eGc1UdbMCQcT5HWX .node circle,#mermaid-svg-eGc1UdbMCQcT5HWX .node ellipse,#mermaid-svg-eGc1UdbMCQcT5HWX .node polygon,#mermaid-svg-eGc1UdbMCQcT5HWX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eGc1UdbMCQcT5HWX .rough-node .label text,#mermaid-svg-eGc1UdbMCQcT5HWX .node .label text,#mermaid-svg-eGc1UdbMCQcT5HWX .image-shape .label,#mermaid-svg-eGc1UdbMCQcT5HWX .icon-shape .label{text-anchor:middle;}#mermaid-svg-eGc1UdbMCQcT5HWX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eGc1UdbMCQcT5HWX .rough-node .label,#mermaid-svg-eGc1UdbMCQcT5HWX .node .label,#mermaid-svg-eGc1UdbMCQcT5HWX .image-shape .label,#mermaid-svg-eGc1UdbMCQcT5HWX .icon-shape .label{text-align:center;}#mermaid-svg-eGc1UdbMCQcT5HWX .node.clickable{cursor:pointer;}#mermaid-svg-eGc1UdbMCQcT5HWX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eGc1UdbMCQcT5HWX .arrowheadPath{fill:#333333;}#mermaid-svg-eGc1UdbMCQcT5HWX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eGc1UdbMCQcT5HWX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eGc1UdbMCQcT5HWX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eGc1UdbMCQcT5HWX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eGc1UdbMCQcT5HWX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eGc1UdbMCQcT5HWX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eGc1UdbMCQcT5HWX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eGc1UdbMCQcT5HWX .cluster text{fill:#333;}#mermaid-svg-eGc1UdbMCQcT5HWX .cluster span{color:#333;}#mermaid-svg-eGc1UdbMCQcT5HWX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eGc1UdbMCQcT5HWX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eGc1UdbMCQcT5HWX rect.text{fill:none;stroke-width:0;}#mermaid-svg-eGc1UdbMCQcT5HWX .icon-shape,#mermaid-svg-eGc1UdbMCQcT5HWX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eGc1UdbMCQcT5HWX .icon-shape p,#mermaid-svg-eGc1UdbMCQcT5HWX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eGc1UdbMCQcT5HWX .icon-shape .label rect,#mermaid-svg-eGc1UdbMCQcT5HWX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eGc1UdbMCQcT5HWX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eGc1UdbMCQcT5HWX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eGc1UdbMCQcT5HWX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 命中
未命中
客户端发起连接
连接器:认证与权限校验
查询缓存(8.0 已移除)
直接返回结果
分析器:词法/语法分析
优化器:生成执行计划
执行器:调用存储引擎接口
存储引擎:InnoDB 读取数据页
返回结果集给客户端

下面按这条链路逐层展开。

3. 第一步:连接器

客户端(如 JDBC、Navicat、mysql 命令行)首先需要与 MySQL 服务端建立连接。这一步由连接器负责,主要做三件事:

  1. TCP 握手:客户端与服务端建立 TCP 连接。
  2. 身份认证 :校验用户名、密码,读取 mysql.user 表中的账号信息。
  3. 权限校验 :认证通过后,读取该用户拥有的权限,此后该连接内的所有操作都基于此时读取到的权限。也就是说,即使管理员中途修改了权限,已建立的连接也不会立即生效,需要重新连接。

连接建立后,连接会保持空闲状态,直到客户端发送 SQL 或超时断开。空闲连接的默认超时时间由 wait_timeout 控制(默认 8 小时)。

这里有两个常见问题值得注意:

  • 连接数打满 :max_connections 达到上限后,新连接会被拒绝,报 Too many connections。长连接过多是常见原因,可通过连接池控制或定期重连。
  • 长连接内存膨胀 :MySQL 在执行过程中临时使用的内存是管理在连接对象里的,长连接累积可能导致内存占用过大。可以通过 mysql_reset_connection 重置连接状态。

4. 第二步:查询缓存(MySQL 8.0 已移除)

在 MySQL 5.7 及之前,连接建立后,如果开启了查询缓存,服务端会先检查这条 SQL 是否命中缓存:

  • 命中:直接返回缓存结果,不再走后续流程。
  • 未命中:继续执行,并把结果写入缓存。

查询缓存以 SQL 文本为 key,只要表上有任何更新,该表相关的所有缓存都会失效。因此在写多读少的场景下,缓存命中率极低,反而带来额外的维护开销。这也是 MySQL 8.0 直接移除查询缓存的原因。

提示:如果你还在使用 5.7,建议通过 query_cache_type=0 关闭查询缓存,把缓存交给 Redis 等外部组件。

5. 第三步:分析器

未命中缓存后,SQL 进入分析器,分为两个阶段:

5.1 词法分析

把 SQL 字符串拆解成一个个 token,识别出关键字、表名、列名、常量等。例如:

sql 复制代码
SELECT id, name, age FROM user WHERE id = 1;

会被识别为:SELECT(关键字)、id/name/age(列名)、FROM(关键字)、user(表名)、WHERE(关键字)、id(列名)、=(运算符)、1(常量)。

5.2 语法分析

根据词法分析的结果,按照 MySQL 的语法规则判断这条语句是否合法。如果语法有误,会报错:

text 复制代码
ERROR 1064 (42000): You have an error in your SQL syntax...

语法通过后,分析器还会做语义检查,比如表是否存在、列是否存在、是否有权限访问等。

6. 第四步:优化器

语法合法后,进入优化器。优化器不关心数据本身,它负责决定「怎么执行最快」,主要产出两件事:

  1. 选择索引:如果一个查询有多个可用索引,优化器会根据统计信息(如区分度、基数)估算各方案的代价,选择代价最小的索引。
  2. 决定 join 顺序:多表关联时,决定先查哪张表、后查哪张表。

例如:

sql 复制代码
SELECT * FROM t1 JOIN t2 ON t1.id = t2.id WHERE t1.a = 1 AND t2.b = 2;

优化器可能选择先过滤 t1 再关联 t2,也可能反过来,取决于哪张表过滤后数据量更小。

优化器的选择并不总是最优的,统计信息过期、索引区分度低、隐式类型转换等都可能导致选错索引。这时可以用 EXPLAIN 查看执行计划,必要时用 FORCE INDEX 干预。

7. 第五步:执行器

优化器给出执行计划后,交给执行器执行。执行器的工作流程大致如下:

  1. 判断当前用户对目标表是否有查询权限,没有则返回权限错误。
  2. 打开表,根据执行计划调用存储引擎提供的接口。
  3. 以 SELECT id, name, age FROM user WHERE id = 1 为例,假设 id 是主键:
    • 调用 InnoDB 接口取 id = 1 这一行;
    • 判断 id = 1 是否满足条件(主键查询时引擎已保证,但执行器仍会判断);
    • 满足则加入结果集,不满足则跳过;
    • 继续取下一行,直到表遍历结束。
  4. 把结果集返回给客户端。

对于没有索引的查询,执行器会调用引擎接口逐行读取,这就是所谓的全表扫描,也是慢查询的常见来源。

8. 第六步:存储引擎层(以 InnoDB 为例)

执行器调用的是存储引擎的统一接口,具体的数据读取由 InnoDB 完成。以主键查询为例,InnoDB 的读取过程大致是:

  1. 定位数据页:根据主键值在 B+ 树中从根节点逐层向下查找,定位到叶子节点所在的 16KB 数据页。
  2. 检查 Buffer Pool:如果目标页已在 Buffer Pool 中,直接读取;否则从磁盘加载到 Buffer Pool。
  3. 页内查找:在数据页内通过页目录(Page Directory)二分查找定位到具体记录。
  4. 返回记录:把记录返回给执行器。

如果是二级索引查询,还需要回表:先在二级索引 B+ 树中找到主键值,再拿主键去聚簇索引中查完整行。回表次数过多时,优化器可能放弃二级索引而选择全表扫描。

9. 更新语句的执行差异

UPDATE 语句的前半段流程与查询一致(连接器 → 分析器 → 优化器 → 执行器),区别在于执行器调用引擎接口后,InnoDB 内部还要完成日志写入:

sql 复制代码
UPDATE user SET age = 20 WHERE id = 1;

执行过程大致如下:

  1. 执行器调用 InnoDB 接口读取 id = 1 这一行,如果该行所在数据页在 Buffer Pool 中则直接返回,否则从磁盘加载。
  2. 执行器把 age 改为 20,调用引擎接口写入新行。
  3. InnoDB 将新行写入 Buffer Pool,同时把修改记录写入 redo log ,此时 redo log 处于 prepare 状态。
  4. 执行器生成该操作的 binlog 并写入磁盘。
  5. 执行器调用引擎接口提交事务,InnoDB 把 redo log 改为 commit 状态。

这就是常说的两阶段提交 。它保证了 redo log 与 binlog 的一致性:如果 redo log 写完 prepare 后崩溃,恢复时发现 binlog 不完整,则回滚;如果 binlog 写完但 redo log 未 commit,恢复时根据 binlog 补提交。

此外,更新前 InnoDB 还会把旧值写入 undo log,用于事务回滚和 MVCC 快照读。

10. 完整链路小结

把上面的过程串起来,一条查询语句的完整执行链路是:
#mermaid-svg-CSd43iNTb1YzKFlo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CSd43iNTb1YzKFlo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CSd43iNTb1YzKFlo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CSd43iNTb1YzKFlo .error-icon{fill:#552222;}#mermaid-svg-CSd43iNTb1YzKFlo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CSd43iNTb1YzKFlo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CSd43iNTb1YzKFlo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CSd43iNTb1YzKFlo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CSd43iNTb1YzKFlo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CSd43iNTb1YzKFlo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CSd43iNTb1YzKFlo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CSd43iNTb1YzKFlo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CSd43iNTb1YzKFlo .marker.cross{stroke:#333333;}#mermaid-svg-CSd43iNTb1YzKFlo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CSd43iNTb1YzKFlo p{margin:0;}#mermaid-svg-CSd43iNTb1YzKFlo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CSd43iNTb1YzKFlo .cluster-label text{fill:#333;}#mermaid-svg-CSd43iNTb1YzKFlo .cluster-label span{color:#333;}#mermaid-svg-CSd43iNTb1YzKFlo .cluster-label span p{background-color:transparent;}#mermaid-svg-CSd43iNTb1YzKFlo .label text,#mermaid-svg-CSd43iNTb1YzKFlo span{fill:#333;color:#333;}#mermaid-svg-CSd43iNTb1YzKFlo .node rect,#mermaid-svg-CSd43iNTb1YzKFlo .node circle,#mermaid-svg-CSd43iNTb1YzKFlo .node ellipse,#mermaid-svg-CSd43iNTb1YzKFlo .node polygon,#mermaid-svg-CSd43iNTb1YzKFlo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CSd43iNTb1YzKFlo .rough-node .label text,#mermaid-svg-CSd43iNTb1YzKFlo .node .label text,#mermaid-svg-CSd43iNTb1YzKFlo .image-shape .label,#mermaid-svg-CSd43iNTb1YzKFlo .icon-shape .label{text-anchor:middle;}#mermaid-svg-CSd43iNTb1YzKFlo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CSd43iNTb1YzKFlo .rough-node .label,#mermaid-svg-CSd43iNTb1YzKFlo .node .label,#mermaid-svg-CSd43iNTb1YzKFlo .image-shape .label,#mermaid-svg-CSd43iNTb1YzKFlo .icon-shape .label{text-align:center;}#mermaid-svg-CSd43iNTb1YzKFlo .node.clickable{cursor:pointer;}#mermaid-svg-CSd43iNTb1YzKFlo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CSd43iNTb1YzKFlo .arrowheadPath{fill:#333333;}#mermaid-svg-CSd43iNTb1YzKFlo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CSd43iNTb1YzKFlo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CSd43iNTb1YzKFlo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CSd43iNTb1YzKFlo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CSd43iNTb1YzKFlo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CSd43iNTb1YzKFlo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CSd43iNTb1YzKFlo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CSd43iNTb1YzKFlo .cluster text{fill:#333;}#mermaid-svg-CSd43iNTb1YzKFlo .cluster span{color:#333;}#mermaid-svg-CSd43iNTb1YzKFlo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CSd43iNTb1YzKFlo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CSd43iNTb1YzKFlo rect.text{fill:none;stroke-width:0;}#mermaid-svg-CSd43iNTb1YzKFlo .icon-shape,#mermaid-svg-CSd43iNTb1YzKFlo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CSd43iNTb1YzKFlo .icon-shape p,#mermaid-svg-CSd43iNTb1YzKFlo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CSd43iNTb1YzKFlo .icon-shape .label rect,#mermaid-svg-CSd43iNTb1YzKFlo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CSd43iNTb1YzKFlo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CSd43iNTb1YzKFlo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CSd43iNTb1YzKFlo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端
连接器
查询缓存(8.0 移除)
分析器
优化器
执行器
存储引擎 InnoDB
Buffer Pool / 磁盘

各阶段职责速查:

阶段 核心职责 常见问题
连接器 认证、权限、连接管理 连接数打满、长连接内存膨胀
查询缓存 命中则直接返回(8.0 已移除) 写多读少时命中率低、维护开销大
分析器 词法分析、语法分析、语义检查 语法错误、表/列不存在
优化器 选索引、定 join 顺序 选错索引、统计信息过期
执行器 权限校验、调用引擎接口、组装结果 全表扫描、回表过多
存储引擎 B+ 树查找、Buffer Pool、日志 磁盘 IO 高、锁等待

11. 总结

一条 SQL 在 MySQL 中的执行过程,本质上是分层协作的过程:

  • Server 层负责「理解 SQL 并决定怎么执行」;
  • 存储引擎层负责「真正把数据取出来或写进去」。

理解这条链路的价值在于:当遇到慢查询时,你能判断是连接层、解析层、优化层还是存储层的问题;当遇到数据不一致时,你能想到两阶段提交与日志顺序;当遇到连接异常时,你能想到连接器与连接池配置。

建议结合 EXPLAIN、SHOW PROFILE、performance_schema 等工具,把理论链路和实际执行计划对照起来看,理解会更扎实。

相关推荐
Canmag 兴隆磁性1 小时前
钕铁硼磁片取向方向检测自动化方案:从人工目检到流水线在线分选的工程实践
运维·数据库·自动化·工业自动化·钕铁硼·自动化检测·磁材料
月夏1 小时前
一个汉字存成了 6 个字节:我用 HEX 揪出 MySQL 双重编码
mysql·docker·全栈
yangmu32031 小时前
安全审批与日志审计:从“谁批准”到“谁执行”的全链路闭环
数据库
R²AIN SUITE1 小时前
多云或私有云环境怎么选项目管理工具?RainSuite等5种产品的8项部署验证
服务器·网络·数据库
隔振降噪研究员1 小时前
裁断机一响,楼板为什么会跟着抖?
大数据·linux·数据库·人工智能
软件派3 小时前
生产动态数据智能风控:阈值判别、趋势预测、异常识别算法工程落地实战
前端·数据库·算法
ss27310 小时前
PostgreSQL常见索引问题与解决方案
数据库·postgresql
小徐不徐说10 小时前
序列化与反序列化:把 C++ 对象可靠地保存下来
开发语言·数据库·c++·php
瀚高PG实验室12 小时前
发布端逻辑复制槽消失
数据库·postgresql·瀚高数据库