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 服务端建立连接。这一步由连接器负责,主要做三件事:
- TCP 握手:客户端与服务端建立 TCP 连接。
- 身份认证 :校验用户名、密码,读取
mysql.user表中的账号信息。 - 权限校验 :认证通过后,读取该用户拥有的权限,此后该连接内的所有操作都基于此时读取到的权限。也就是说,即使管理员中途修改了权限,已建立的连接也不会立即生效,需要重新连接。
连接建立后,连接会保持空闲状态,直到客户端发送 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. 第四步:优化器
语法合法后,进入优化器。优化器不关心数据本身,它负责决定「怎么执行最快」,主要产出两件事:
- 选择索引:如果一个查询有多个可用索引,优化器会根据统计信息(如区分度、基数)估算各方案的代价,选择代价最小的索引。
- 决定 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. 第五步:执行器
优化器给出执行计划后,交给执行器执行。执行器的工作流程大致如下:
- 判断当前用户对目标表是否有查询权限,没有则返回权限错误。
- 打开表,根据执行计划调用存储引擎提供的接口。
- 以
SELECT id, name, age FROM user WHERE id = 1为例,假设id是主键:- 调用 InnoDB 接口取
id = 1这一行; - 判断
id = 1是否满足条件(主键查询时引擎已保证,但执行器仍会判断); - 满足则加入结果集,不满足则跳过;
- 继续取下一行,直到表遍历结束。
- 调用 InnoDB 接口取
- 把结果集返回给客户端。
对于没有索引的查询,执行器会调用引擎接口逐行读取,这就是所谓的全表扫描,也是慢查询的常见来源。
8. 第六步:存储引擎层(以 InnoDB 为例)
执行器调用的是存储引擎的统一接口,具体的数据读取由 InnoDB 完成。以主键查询为例,InnoDB 的读取过程大致是:
- 定位数据页:根据主键值在 B+ 树中从根节点逐层向下查找,定位到叶子节点所在的 16KB 数据页。
- 检查 Buffer Pool:如果目标页已在 Buffer Pool 中,直接读取;否则从磁盘加载到 Buffer Pool。
- 页内查找:在数据页内通过页目录(Page Directory)二分查找定位到具体记录。
- 返回记录:把记录返回给执行器。
如果是二级索引查询,还需要回表:先在二级索引 B+ 树中找到主键值,再拿主键去聚簇索引中查完整行。回表次数过多时,优化器可能放弃二级索引而选择全表扫描。
9. 更新语句的执行差异
UPDATE 语句的前半段流程与查询一致(连接器 → 分析器 → 优化器 → 执行器),区别在于执行器调用引擎接口后,InnoDB 内部还要完成日志写入:
sql
UPDATE user SET age = 20 WHERE id = 1;
执行过程大致如下:
- 执行器调用 InnoDB 接口读取
id = 1这一行,如果该行所在数据页在 Buffer Pool 中则直接返回,否则从磁盘加载。 - 执行器把
age改为 20,调用引擎接口写入新行。 - InnoDB 将新行写入 Buffer Pool,同时把修改记录写入 redo log ,此时 redo log 处于
prepare状态。 - 执行器生成该操作的 binlog 并写入磁盘。
- 执行器调用引擎接口提交事务,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 等工具,把理论链路和实际执行计划对照起来看,理解会更扎实。