MySQL


MySQL

聊聊 MySQL 执行 SQL 语句的过程

一条 SQL 语句从查询到返回数据,可以分为 5 个阶段,分别是:

  1. 建立连接。 客户端会首先与 MySQL 建立 TCP 连接,在连接器中会进行连接管理、权限验证等操作。
  2. 分析 SQL。 分析器进行词法、语法分析,词法分析知道要查询什么内容,语法分析判断语法是否有问题。
  3. 优化 SQL。 优化器根据 SQL 情况,判断使用哪种执行方式更好,例如使用哪个索引,哪种表连接方式。
  4. 执行 SQL。 根据优化器的优化结果,生成执行计划,执行器调用存储引擎的API来执行查询,最终将数据返回给客户端。

简单地说,这五个阶段为:建立连接、分析器分析、优化器优化、执行器查询。

聊聊 MySQL 写入数据时的二阶段提交

当 MySQL 查询到数据之后,下一步就需要写入(更新)数据。对于写入数据,MySQL 使用了二阶段提交的方式来保证消息的一致性。其过程如下所示:

  1. 第 1 步:写入 redolog prepare 状态。 当将内存的数据更新完之后,先将数据写入 redo log 文件,此时该 redo log 消息还是处于 prepare 状态。
  2. 第 2 步:写入 binlog 文件。 上一步写入完成,那么就继续写入 binlog。
  3. 第 3 步:提交事务,将 redolog 改为 commit 状态。 这一步就提交事务,并且将 redolog 改为 commit 状态。

通过把 redolog 拆分成 prepare 阶段和 commit 阶段,实现了 binlog 和 redolog 的一致。这样的二阶段操作,使得无论在哪一步发生异常,都不会发生数据不一致的问题。

为什么 MySQL 要有二阶段提交?它能解决什么问题?

首先,我们需要知道:redo log 是用于提高写入性能的,binlog 是用于方便做备份恢复的。因此,我们这个问题讨论的前提是:redo log 和 binlog 都存在。

那我们假设没有二阶段提交,只有写 redo log 和 写 binlog 这两个过程。那么一共有两种情况,我们用 update T set c=c+1 where ID=2; 语句来分析一下。

第一种情况:先写 redo log 后写 binlog。

假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。

因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。

第二种情况:先写 binlog 后写 redo log。

如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了「把 c 从 0 改成 1」这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。

可以看到,如果不使用「两阶段提交」,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致,从而出现数据不一致的问题。所以,我们用二阶段提交可以解决数据不一致的问题。

我们再来看看在二阶段提交的情况下,再不同时刻发生 MySQL 重启异常,会出现什么现象。

第一种情况:在时刻 A 出现异常。

如果在时刻 A 发生异常,也就是写入 redo log 处于 prepare 阶段之后、写 binlog 之前,发生了崩溃(crash)。由于此时 binlog 还没写,redo log 也还没提交,所以崩溃恢复的时候,这个事务会回滚。这时候,binlog 还没写,所以也不会传到备库。所以在时刻 A 出现异常,并不会出现问题。

第二种情况:在时刻 B 出现异常。

在时刻 B 发生异常,也就是 binlog 写完,redo log 还没 commit 前发生 crash,那么此时会按照崩溃恢复规则进行判断:

  1. 如果 redo log 里面的事务是完整的,也就是已经有了 commit 标识,则直接提交。
  2. 如果 redo log 里面的事务只有完整的 prepare,则判断对应的事务 binlog 是否存在并完整。如果对应 binlog 完整,则交事务,否则回滚事务。

这里时刻 B 发生的问题,属于有完整的 prepare,并且 binlog 也完整,因此会提交事务。

参考资料:

MySQL 是如何持久化的?

只用 binlog 进行持久化可行吗?为什么一定要 binlog + redo log?

上次编辑于: 2022/7/30 09:07:48