MySQL实战:一条SQL更新语句是如何执行的?

文章目录

上一篇我们知道了一条sql查询语句是则如何执行的,接下来我们看看一条sql更新语句是如何执行的。

首先,可以确定的说,查询语句的那一套流程,更新语句也是同样会走一遍。

连接数据库,这是连接器的工作。上一篇说过,查询缓存会在表更新的时候清空这张表所有的缓存。接下来分析器通过此法和语法解析确定这是一条更新语句。优化器决定索引。然后,执行器负责执行,调用存储引擎接口更新数据。

与查询流程不一样的是,更新流程还涉及到两个重要的日志模块:redo log(重做日志)和 bin log(归档日志)。

重要的日志模块:redo log

如果我们每一次更新都需要写进磁盘,然后磁盘也要找到对应的那条记录,最后更新。这个过程的 IO、查找成本都很高。

为了解决这个问题,MySQL的设计者引入了 WAL 技术,WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。

具体来说,当一条记录需要更新的时候,InnoDB 引擎会先把记录写入 redo log,并更新内存,这个时候更新就算完成了。同时,InnoDB 会在适当的时候,将这个记录更新到磁盘,而这个更新往往是系统比较空闲的时候做。

InnoDB 的 redo log 的大小是固定的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么总共可以记录 4GB 的操作。从头开始写,写到末尾了,这时就不能再执行新的更新,得先停下来更新开头的一部分记录到磁盘并擦除 redo log 中更新过的记录,然后从头开始写。

有了 redo log。InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力成为 crash-safe。

重要的日志模块:binlog

上一篇讲过,MySQL大体分为两块:一块是 server 层,它主要做的是 MySQL 功能层面的事情;另一块是引擎层,负责存储相关的具体事宜。redo log 是 InnoDB 引擎特有的日志,而 server 层也有自己的日志,成为 binlog(归档日志)。

那么,为什么会有两份日志呢?

因为最开始的 MySQL 没有 InnoDB 引擎。MySQL 自带的是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只用于归档。InnoDB 是另外一家公司以插件的形式引入 MySQL 的,只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用 redo log 来实现 crash-safe。

这两种日志有三点不同:

  1. redo log 是 InnoDB 引擎独有的;binlog 是 MySQL 在 server 层实现的,所有存储引擎都可以使用。
  2. redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑。
  3. redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,不会覆盖以前的日是。

理解了两种日志的概念后,我们来看看解释器和 InnoDB 引擎在执行下面这个 update 语句时的流程。

update T set c = c + 1 where ID = 2
  1. 执行器先从存储引擎中找到 ID = 2 的记录。ID 是主键,存储引擎直接用树搜索到这一条记录。如果 ID = 2 这条记录在内存中,直接返回给执行器;如果没在内存中,需要先从磁盘中读入到内存,然后再返回。
  2. 执行器拿到记录后,把 c 的值加上 1,然后调用存储引擎更新这条记录。
  3. 搜索引擎将这条记录更新到内存中,同时将这个更新操作记录到 redo log 中,此时的 redo log 处于 prepare(准备) 状态。然后告诉执行器执行完成,可以随时提交事务。
  4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
  5. 执行器调用搜索引擎的提交事务接口,搜索引擎把刚刚把 redo log 的 prepare 状态修改为 commit 状态,更新完成。

上面五个步骤,把 redo log 的写入拆分成了两个步骤:prepare 和 commit。这就是“两阶段提交”。

两阶段提交

为什么要“两阶段提交”呢?这是为了让两份日志之间的逻辑一致。

如果某天下午两点发现中午十二点有一次误删表,需要找回数据,那你可以这么做:

  1. 首先,找到最近一次的全量备份,如果运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库。
  2. 然后,从备份的时间点开始,将备份的 binlog 依次取出,重放到中午误删表之前的那个时刻。

这样你的临时库就跟误删表之前的线上库一样了,然后你可以把表数据从临时库取出来,按需恢复到线上库去。

理解了数据恢复过程,我们回来看看,为什么日志需要“两阶段提交”。这里我们用反证法来进行解释。

由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么先写完 redo log 在写 binlog,或者采用反过来的顺序。

  1. 先写 redo log 后写 binlog。假设在写完 redo log,binlog 还没写完的时候,系统发生了崩溃,binlog 中没有记录。此时恢复了系统,由于写了 redo log,记录的更新操作是成功的,但是没有 binlog 记录,如果需要用 binlog 来恢复临时库的话,这个临时库就会少一次更新,恢复出来的这一行与原库的值不同。
  2. 先写 binlog 后写 redo log。如果在写完 binlog 后系统崩溃,由于还没写 redo log,所以此时的更新操作无效。但是 binlog 中已经记录了这个日志,当用 binlog 恢复临时库时就多出来一个事务,恢复出来的这一行与原库的值不同。

可以看出来,如果不适用两阶段提交,数据库的状态就可能和用日志恢复出来的库的状态不一致。

建议

redo log 用于保证 crash-safe 能力。innodb_flush_log_at_trx_commit 这个参数设置为 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数建议设置为 1,可以保证 MySQL 异常重启之后数据不会丢失。

sync_binlog 这个参数设置为 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数也建议设置为 1,可以保证 MySQL 异常重启之后 binlog 不会丢失。

原文链接:,转发请注明来源!
评论已关闭。