前言:MySQL 逻辑架构概述

在日常开发中,我们通常会编写 SQL:

SELECT * FROM users WHERE id = 1;

或者:

UPDATE users
SET username = 'admin'
WHERE id = 1;

但很多初学者容易忽略一个问题:

一条 SQL 从发送到执行完成,中间到底发生了什么?

下面是 MySQL 的基本架构示意图,从中我们可以清楚地看到 SQL 语句在 MySQL的各个功能模块中的执行过程。
在这里插入图片描述

从客户端发送请求,到SQL被解析、优化、执行,再到最后数据落盘,这中间涉及:Server层、存储引擎层、Buffer Pool、Redo Log、Undo Log、两阶段提交(2PC)、脏页刷新机制…

本文将以 MySQL 8.0 + InnoDB 为核心,结合 MySQL 执行流程图,完整分析:

一条 SQL 在 MySQL 内部是如何运行的。

一、Server层:SQL 解析、优化与分发

Server 层包括连接器、查询缓存、分析器、优化器、执行器等组件,负责 SQL 的解析、优化、执行调度以及权限管理等核心逻辑,可以理解为 MySQL 的“大脑”。
下面将一一介绍Server层各大组件。

Connector - 连接器

客户端连接MySQL时,首先进入连接器。
当我们执行:

mysql -uroot -p

连接器负责跟客户端建立连接、获取权限、维持和管理连接。

Query Cache - 查询缓存

在 MySQL 5.7 中,当客户端发送查询请求后,MySQL 会优先检查查询缓存(Query Cache),判断该 SQL 是否已经执行过。如果存在相同 SQL 的缓存结果,则直接返回,无需继续后续解析、优化和执行流程。

MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个value 就会被直接返回给客户端。

如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。
如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。

但问题在于缓存失效成本极高,只要表发生变化,整个缓存都会失效,因此在高并发环境下收益极低,所以MySQL 8.0 已彻底移除查询缓存

Parser - 分析器

接续上一个步骤,如果缓存未命中,SQL会进入分析器。

分析器包括两个部分,分别是词法分析和语法分析。

词法分析

词法分析(Lexical Analysis)的核心作用是:将 SQL 拆分成一个个“词元(Token)”。
例如:

SELECT name FROM user WHERE id = 1;

这条语句会先被分析器拆成:

  • SELECT - 关键字
  • name - 字段
  • FROM
  • user - 表
  • WHERE
  • id - 字段
  • = (运算符)
  • 1 - 常量
    如果关键字错误,则会报错:
You have an error in your SQL syntax

如果没有报错,则进入下一阶段的语法分析。

语法分析

根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。

如果 SQL 语句不对,就会返回 You have an error in your SQL syntax 的错误提醒,一般语法错误会提示第一个出现错误的位置,所以你要关注的是紧接use near的内容。

如果语法校验通过,MySQL 会进一步生成对应的语法树(AST,Abstract Syntax Tree),供后续优化器生成执行计划。

Optimizer - 优化器

解析完成后进入优化器,它会对你输入的sql语句,生成最优执行计划。

同样一条 SQL :

SELECT * 
FROM user
WHERE age = 20
AND city = 'Beijing';

可能走age索引、走city索引、走联合索引、全表扫描,优化器会根据索引统计信息以及成本估算(Cost Based Optimizer),最终生成执行计划。

我们可以通过 EXPLAIN 查看优化器最终生成的执行计划。

EXPLAIN
SELECT *
FROM user
WHERE id = 1;

Executor - 执行器

优化器生成执行计划后,执行器开始真正执行 SQL。

执行器会再次进行用户权限校验,判断用户是否允许SELECTUPDATEDELETE,然后才调用存储引擎(InnoDB )API,让存储引擎执行。所以Server 层与存储引擎是解耦的。

此外,Server 层还维护着自己的日志系统 —— binlog(归档日志),它主要记录对数据库产生修改的操作,例如 INSERT、UPDATE、DELETE 等语句。
不过:

为什么 MySQL 同时存在 redo log 与 binlog?
二者又是如何保证事务一致性的?

这个问题将在后文的“两阶段提交(2PC)”中展开分析。

二、存储引擎层:InnoDB 核心组件与数据页管理

真正的数据读写发生在存储引擎层,MySQL 支持多种引擎,如InnoDB、MyISAM、Memory等,每种引擎都有其特点和适用场景。

特别注意:

InnoDB 是 MySQL 的默认存储引擎,它支持事务、行级锁定和外键约束。InnoDB 有自己的日志系统,称为 redo log(重做日志) 和 undo log(撤销日志)。redo log 用于保证事务的持久性,在数据库崩溃后可以用来恢复数据;undo log 用于支持事务的原子性和多版本并发控制(MVCC)。
redo log 属于 InnoDB 存储引擎层,而 binlog 属于 Server 层,这也是后续两阶段提交机制出现的根本原因。

InnoDB 内部执行流程(写操作)

以下列语句举例:

UPDATE account
SET balance = 100
WHERE id = 1;
  1. 第一步:读取数据页到 Buffer Pool
    InnoDB 不会直接修改磁盘,而是先读入Buffer Pool 缓冲池,这是Mysql的内存缓冲区,从而减少随机磁盘 IO,提高读写性能。
  2. 第二步:写 Undo Log
    即更新前保存旧值,一是方便 ROLLBACK 回滚事务恢复旧数据,二是实现 MVCC(多版本并发控制),让不同事务读取不同版本的数据,从而减少锁竞争并避免读写冲突。
  3. 第三步:写Redo Log
    修改内存页后记录Redo Log 重做日志,属于是WAL(Write Ahead Logging),即先写日志,再写磁盘。当数据库异常宕机导致数据未及时落盘时,可以通过 redo log 重放日志恢复数据。

三、核心链路:更新事务的两阶段提交(2PC)机制

在第二部分中我们提到: redo log 属于 InnoDB 存储引擎层,而 binlog 属于 Server 层。
那么问题来了:为什么 MySQL 需要两套日志?
直接使用 redo log 不行吗?
答案是:不行。

因为两者职责不同:

日志 所属层 核心作用 特点
redo log InnoDB 崩溃恢复 物理日志
binlog Server 主从复制、数据恢复 逻辑日志
当你执行:
UPDATE account
SET balance = 100
WHERE id = 1;

如果redo log 写成功 、binlog 写失败。
数据库崩溃恢复后,本地数据库恢复成功,但主库同步给从库时,由于 binlog 丢失,从库数据不会更新,此时主从数据不一致。

反过来,如果binlog 写成功,redo log 写失败。
那么恢复后,主库事务丢失,但从库已经同步成功,依然数据不一致。

因此,MySQL 引入了两阶段提交,用于保证 redo log 与 binlog 的一致性。

两阶段提交执行流程

整个流程如下:

  1. 第一阶段:写 redo log(prepare)
    当事务执行时,InnoDB 会先写入 redo log ,但此时不会直接提交,而是进入prepare状态,表示redo log 已写入,但事务还未真正完成,目的是等待 binlog 的写入。
  2. 第二阶段:写 binlog
    redo log 进入 prepare 状态后,Server 层开始写 binlog 归档日志,记录此次 SQL 的修改信息。
  3. 第三阶段:提交 redo log (commit)
    两个日志都写入完成后,InnoDB 将redo log状态从prepare改成commit,表示事务真正提交成功。
    整个流程如图:
    在这里插入图片描述

为什么 2PC 能保证一致性?

假设数据库突然崩溃,MySQL 恢复时,会检查redo log 状态:

  • 如果 redo log 是commit:说明事务已经完成,直接恢复
  • 如果 redo log是 prepare:此时 MySQl 会进一步检查binlog是否存在:
    • 如果 binlog 已存在,说明事务已经执行成功,恢复事务
    • 如果 binlog 不存在,说明事务未完成,执行回滚
      因此 MySQL 即使在宕机的情况下,也能保证 redo log 与 binlog 的最终一致性,这也是MySQL 事务可靠性的核心机制之一。

四、物理磁盘层:脏页的异步刷新

很多人容易误解:执行 COMMIT 后,数据已经立刻写入磁盘,其实不一定。
真正立即写入磁盘的是redo log,而数据页本身通常还停留在内存中的Buffer Pool

修改后的内存页称为Dirty Page(脏页),这些脏页不会立即刷盘,而是由后台线程Flush Thread,异步完成。

这样做的原因是:磁盘随机写成本很高。如果每次更新都立即写磁盘,MySQL 性能会大幅下降。因此MySQL 采用:

内存修改 + 日志持久化

从而提升性能。

脏页什么时候刷盘?

一般有以下几种情况:

  1. Redo log 快满:必须先把脏页刷盘,释放空间。
  2. Buffer Pool 空间不足:缓存页满时,需要淘汰旧页,如果是脏页,必须先落盘。
  3. 后台线程定时刷新:MySQL 会周期性执行checkpoint将部分脏页写回磁盘,以降低系统崩溃恢复成本。即使系统突然崩溃,只要 redo log 存在,MySQL 依然能够恢复数据。

checkpoint 的作用是记录当前 redo log 可以安全覆盖的位置,从而避免 redo log 无限增长。

五、结语与架构总结

最后我们把整个 SQL 生命周期串起来。
一条 SQL 请求:从发送到执行完成,大致会经历如下流程:
在这里插入图片描述

理解 MySQL 的执行流程,本质上是在理解数据库系统是如何在 性能、可靠性与一致性之间做平衡
MySQL 之所以能够支撑高并发场景,并不是因为某一个组件足够强大,而是:

Server 层、存储引擎、日志系统与刷盘机制共同协作的结果。

当我们真正理解:

  • SQL 如何被解析与优化
  • 数据如何进入 Buffer Pool
  • redo log 与 undo log 如何保证事务
  • 2PC 如何保证日志一致性
  • 脏页如何异步落盘
    才算真正迈出了:

从“会写 SQL”到“理解 MySQL”的关键一步。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐