查询SELECT

假设SELECT id, name FROM users WHERE age = 18;

  1. 连接器 (Connector)

    • 客户端(如 Java 应用或 Navicat)发送 SQL。连接器负责握手、身份认证、权限校验。
    • 如果用户名密码错误,直接拒绝。认证通过后,连接器会读取该用户的权限,后续所有操作都基于此时读取到的权限。
  2. 解析器 (Parser)

    • 词法分析:将 SQL 字符串拆分成一个个“单词”。识别出 SELECT 是关键字,users 是表名,age 是列名。
    • 语法分析:根据 MySQL 的语法规则,判断这条 SQL 语法是否正确。如果打错了关键字(如写成 SELEKT),会在这里报错:You have an error in your SQL syntax。
  3. 预处理器 (Preprocessor)

    • 进一步检查解析树的合法性。
    • 判断 users 这张表在数据库里是否存在,id、name、age 这些列是否存在。如果不存在,则报错。
  4. 优化器 (Optimizer)

    • 核心阶段:一条 SQL 可以有多种执行路径(例如:是走 age 的索引,还是全表扫描?),优化器会基于**成本(Cost)**算出最高效的执行方案。
    • 最终它会生成一个所谓的“执行计划”(我们用 EXPLAIN 看到的结果就是它算出来的)。
  5. 执行器 (Executor)

    • 开始执行前,先判断该用户对 users 表有没有查询权限(由于有触发器或视图的存在,权限校验放在执行器开始时进行)。
    • 有权限则根据优化器的指令,调用底层存储引擎提供的 API 接口。
  6. 存储引擎层 (Storage Engine,以 InnoDB 为例)

    • 执行器调用“取满足条件的第一行”接口。
    • InnoDB 收到请求,先去内存的 Buffer Pool(缓冲池) 中找。如果命中则直接读取;如果不在内存中,就通过后台 I/O 线程去磁盘的 .ibd 文件中把对应的数据页加载到内存中。
    • 引擎将行记录返回给执行器。执行器判断如果满足 age = 18,就送入结果集。
    • 循环调用“取下一行”接口,直到表中的记录遍历完毕。执行器最后将结果集整体返回给客户端。

修改UPDATE/INSERT/DELETE

UPDATE users SET name = '张三' WHERE id = 1;

  1. 前置准备:

    • 与 SELECT 类似,SQL 经过连接器、解析器、预处理器、优化器,最终交由执行器。
  2. 读取数据:

    • 执行器调用 InnoDB 接口,查询 id = 1 的数据。InnoDB 检查该行所在的数据页是否在 Buffer Pool 中。如果不在,先从磁盘读取到内存。
  3. 记录旧值(Undo Log):

    • 在内存中修改前,InnoDB 会先把旧数据(比如原名 ‘李四’)记录到 Undo Log(回滚日志) 中。如果后续事务失败,可以靠它回滚;同时它也为并发事务的 MVCC 提供了历史版本。
  4. 修改内存(脏页):

    • InnoDB 在 Buffer Pool 中将 id = 1 的行记录中的 name 修改为 '张三'。此时,内存中的数据页和磁盘不一致了,该页变成脏页。
  5. 写重做日志(Redo Log - Prepare 阶段):

    • InnoDB 将本次修改行为(物理日志:“在哪个数据页的哪个位置改了什么”)记录到 Redo Log Buffer 中,并根据策略刷入磁盘。此时将该事务的 Redo Log 状态标记为 Prepare(准备状态)。
  6. 写归档日志(Binlog):

    • 执行器生成本次操作的 Binlog(逻辑日志,记录 SQL 变更),并将其写入 Server 层的 Binlog 文件并刷盘。
  7. 事务提交(Redo Log - Commit 阶段):

    • 执行器调用存储引擎的提交事务接口。InnoDB 收到指令,把刚刚处于 Prepare 状态的 Redo Log 状态改为 Commit(提交状态)。至此,对于客户端来说,事务已经正式提交成功。
  8. 异步刷盘:

    • 此时磁盘数据文件(.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 回滚该事务。