查询SELECT
假设SELECT id, name FROM users WHERE age = 18;
-
连接器 (Connector)
- 客户端(如 Java 应用或 Navicat)发送 SQL。连接器负责握手、身份认证、权限校验。
- 如果用户名密码错误,直接拒绝。认证通过后,连接器会读取该用户的权限,后续所有操作都基于此时读取到的权限。
-
解析器 (Parser)
- 词法分析:将 SQL 字符串拆分成一个个“单词”。识别出
SELECT是关键字,users是表名,age是列名。 - 语法分析:根据 MySQL 的语法规则,判断这条 SQL 语法是否正确。如果打错了关键字(如写成
SELEKT),会在这里报错:You have an error in your SQL syntax。
- 词法分析:将 SQL 字符串拆分成一个个“单词”。识别出
-
预处理器 (Preprocessor)
- 进一步检查解析树的合法性。
- 判断
users这张表在数据库里是否存在,id、name、age这些列是否存在。如果不存在,则报错。
-
优化器 (Optimizer)
- 核心阶段:一条 SQL 可以有多种执行路径(例如:是走
age的索引,还是全表扫描?),优化器会基于**成本(Cost)**算出最高效的执行方案。 - 最终它会生成一个所谓的“执行计划”(我们用
EXPLAIN看到的结果就是它算出来的)。
- 核心阶段:一条 SQL 可以有多种执行路径(例如:是走
-
执行器 (Executor)
- 开始执行前,先判断该用户对
users表有没有查询权限(由于有触发器或视图的存在,权限校验放在执行器开始时进行)。 - 有权限则根据优化器的指令,调用底层存储引擎提供的 API 接口。
- 开始执行前,先判断该用户对
-
存储引擎层 (Storage Engine,以 InnoDB 为例)
- 执行器调用“取满足条件的第一行”接口。
- InnoDB 收到请求,先去内存的 Buffer Pool(缓冲池) 中找。如果命中则直接读取;如果不在内存中,就通过后台 I/O 线程去磁盘的
.ibd文件中把对应的数据页加载到内存中。 - 引擎将行记录返回给执行器。执行器判断如果满足
age = 18,就送入结果集。 - 循环调用“取下一行”接口,直到表中的记录遍历完毕。执行器最后将结果集整体返回给客户端。
修改UPDATE/INSERT/DELETE
UPDATE users SET name = '张三' WHERE id = 1;
-
前置准备:
- 与 SELECT 类似,SQL 经过连接器、解析器、预处理器、优化器,最终交由执行器。
-
读取数据:
- 执行器调用 InnoDB 接口,查询
id = 1的数据。InnoDB 检查该行所在的数据页是否在 Buffer Pool 中。如果不在,先从磁盘读取到内存。
- 执行器调用 InnoDB 接口,查询
-
记录旧值(Undo Log):
- 在内存中修改前,InnoDB 会先把旧数据(比如原名 ‘李四’)记录到 Undo Log(回滚日志) 中。如果后续事务失败,可以靠它回滚;同时它也为并发事务的 MVCC 提供了历史版本。
-
修改内存(脏页):
- InnoDB 在 Buffer Pool 中将
id = 1的行记录中的 name 修改为'张三'。此时,内存中的数据页和磁盘不一致了,该页变成脏页。
- InnoDB 在 Buffer Pool 中将
-
写重做日志(Redo Log - Prepare 阶段):
- InnoDB 将本次修改行为(物理日志:“在哪个数据页的哪个位置改了什么”)记录到 Redo Log Buffer 中,并根据策略刷入磁盘。此时将该事务的 Redo Log 状态标记为 Prepare(准备状态)。
-
写归档日志(Binlog):
- 执行器生成本次操作的 Binlog(逻辑日志,记录 SQL 变更),并将其写入 Server 层的 Binlog 文件并刷盘。
-
事务提交(Redo Log - Commit 阶段):
- 执行器调用存储引擎的提交事务接口。InnoDB 收到指令,把刚刚处于 Prepare 状态的 Redo Log 状态改为 Commit(提交状态)。至此,对于客户端来说,事务已经正式提交成功。
-
异步刷盘:
- 此时磁盘数据文件(
.ibd)其实还没被修改。在随后的时间里,InnoDB 的后台线程(Page Cleaner)会异步将 Buffer Pool 中的脏页通过**双写缓冲区(Doublewrite Buffer)**安全地刷新到磁盘的数据文件中。
- 此时磁盘数据文件(
两阶段提交
防止数据库崩溃后,重做日志和归档日志数据不一致
如果不采用两阶段提交,会发生什么?
- 先写 Redo Log,再写 Binlog:如果写完 Redo Log 后数据库断电崩溃了,Binlog 没写成。重启后,从库或者用 Binlog 恢复出的新库会少这条更新,导致主从数据不一致。
- 先写 Binlog,再写 Redo Log:如果写完 Binlog 数据库崩溃了,Redo Log 没写成。重启后主库由于没有 Redo Log 拒绝了这次修改,但从库依据 Binlog 执行了修改,同样导致主从数据不一致。
有了两阶段提交,崩溃后如何恢复?
MySQL 重启进行崩溃恢复时,会扫描 Redo Log:
- 如果 Redo Log 是 Commit 状态:直接重做。
- 如果 Redo Log 是 Prepare 状态:去检查该事务的 Binlog 是否完整。如果 Binlog 完整,则继续提交该事务;如果 Binlog 不完整,则利用 Undo Log 回滚该事务。