图文并茂,带你了解SQL更新的过程

redo log 和 bin log

在DML语句执行的过程当中,主要会涉及到两个日志——redo log和bin log,而这两个日志是数据库 WAL (Write Ahead Logging,先写日志再写磁盘提升效率) 技术的两大主角。下面我来介绍一下这两个日志。sql

redo log(重作日志)

  • 类型:数据页级别的,记录的是物理日志 (好比某个数据作了是什么更改)。
  • 做用:确保事务的持久性,防止在数据库 crash 的时候上有脏页未写入磁盘,在重启 MySQL 的时候会根据 redo log 进行重作。
  • 产生时间:在事务开始的时候就会产生,而 redo log 的落盘不是在事务提交的时候,而是在事务执行过程当中就会进行 redo log 的写入
  • 释放时间:当内存中的脏页都写入磁盘了,那么相应的 redo log 就会被覆盖

注意: 这里为何说是覆盖是由于 redo log 写日志的特性。redo log 的大小是固定的,因此写 redo log 是循环的覆盖写,你能够理解为,一个环形文件以下图。 数据库

其中,整个环形是两个文件构成的(文件个数和文件大小你能够本身指定),两个文件像连在一块儿同样,其中绿色标识的是 check point ,用来表示当前日志被清理到的头(能够理解为当前有用的 redo log的头),而 write position 表明着当前写入位置(就是当前有用 redo log 的尾)。若是数据库有更新那么 write position 就会向前推,若是 write position 要追上 check point 的时候,那么数据库就会停下来将 check point 向前推(就是清理,此时就是将内存中的脏页进行写入磁盘,对应着上面的释放时间)。 缓存

bin log

bin log 默认是关闭的,须要在配置文件本身设置。并发

  • 类型:数据行级别的,逻辑日志 (有两种形式,一种是 statement ,记录着sql语句,另外一种是 row ,记录着数据行更新前和更新后的内容)。
  • 做用:主要用于实现 MySQL 主从复制,数据备份和数据恢复。
  • 产生时间:在一个事务提交的时候会被写入磁盘。
  • 释放时间:是追加写的,因此不会被覆盖,无释放时间。

DML 的执行流程

若是你对 MySQL 的这两个日志没有了解过的话,上面的特性是很难理解的,若是结合着 DML 语句执行流程就会好理解一点,好比我如今要在数据库的表中更新 id = 1 这一行中的 value 字段。性能

update table set value = value + 1 where id = 1;
复制代码

这个时候更新的大体流程就是这样的优化

  1. 首先 MySQL 的 server 层会经过调用执行器去获取指定数据行
  2. 苦差事固然交给引擎(这里是innodb)来作,InnoDB 首先会去查看当前内存中是否存在该数据行,若是存在之间从内存中取出,若是不在那么会从磁盘中 load 到内存以后再从内存中取出相应数据行。
  3. 而后将数据行进行更新并将新行写入内存中(注意此时确定会产生脏页,后面会了解到)。
  4. 以后就会开始写日志,首先是 redo log的写入(此时进入prepare状态*)。
  5. 第二个写 bin log。
  6. 最后进行事务的提交。

注意:这里的事务提交不只仅是简单的 commit; ,由于这里只是简单的 update 语句,本身自己就是一个事务,因此这里的 commit; 是隐式的。而这里所说的 commit; 还包含了 redo log 的状态转换——从 prepare 到 commit 状态,这是一个很重要的点,后面我会详细解释,你这里须要记住有这么一个东西。 spa

到这里咱们来简单总结一下:线程

DML语句的执行和两个日志——redo log、bin log有着很大的关系,由于须要提升数据库的性能,MySQL 采用了一种 WAL(先写日志再写磁盘) 技术,其中就使用到了这两个日志。主要的流程以下,MySQL会从内存中获取相应的数据行(若是没有先从磁盘 load 到内存中),而后将数据行进行更新并将新行写入内存后进行redo log的写入和 bin log 的写入,在一开始 redo log 是处于 prepare 状态,只有在 bin log 写完而后进行事务提交的时候才会处于 commit 状态3d

不只仅是那么简单

这个时候你确定有几个疑问。日志

redo log是如何保证事务的持久性的?(即当事务执行期间发生 crash ,redo log是如何保证 crash-safe 能力)

bin log是如何完成数据恢复和主从复制的?

上面redo log的 prepare 和 commit 两个状态的存在乎义是什么?

为何要存在两个日志,只要一个不行吗?

为何 WAL 技术能提升数据库性能?

下面我来慢慢回答这些问题。

bin log是如何完成数据恢复和主从复制的

首先最简单的是第二个,bin log是如何完成数据恢复和主从复制的?,看了上面的介绍你们应该也知道了,bin log有这么几个特性。

  1. 追加写,不会像 redo log 那样被覆盖
  2. 记录了完整的逻辑日志,能够利用它进行快速的数据恢复。

因此,当咱们要进行数据恢复的时候能够 使用 bin log 为基础备份出一个和原库同样的备库。当咱们要进行主从复制的时候,可使用 bin log 进行 主从库的同步

redo log是如何保证事务的持久性的

提醒一下,我这里使用的是 “双一配置”(即innodb_flush_log_at_trx_commit = 1 和 sync_binlog = 1 )。sync_binlog = 1的意思是 在事务每次提交的时候都会进行 bin log的持久化。而 innodb_flush_log_at_trx_commit = 1 的意思是事务提交的时候都将 redo log 持久化到磁盘。

因此这里的双一就是在每次事务提交的时候都会进行 redo log 和 bin log 的持久化,这两个参数的其余配置能够去参考其余文章,这里不作过多涉及。

这就不得不提到两阶段提交了,这时候还会牵扯到上面的另外一个问题redo log的 prepare 和 commit 两个状态的存在乎义是什么?

两阶段提交是为了在数据库发生 crash 以后重启恢复可以保证事务完整性。好比这个时候咱们正在进行上面的 update 语句,而后此时数据库宕掉了。为了你好理解我在将上面的流程图拿过来。

你会发现,我这里标注了三个时刻,就是咱们宕机事务可能会执行到的时刻。

首先我先将规则写在前面,大家能够对照着去理解。

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

咱们来看一下上面几个时刻。

  • 时刻A:显而易见,此时日志都没写,东西都在内存中,重启确定会回滚(就当什么事都没发生)。
  • 时刻B:此时redo log 已经写盘,可是只是处于 prepare 状态,若是这时候发生 crash ,那么 bin log还没写 and redo log 还处于 prepare 状态,此时事务会回滚。
  • 时刻C:此时 bin log 已经写盘,redo log 已写盘并处于 prepare 状态,此时事务会根据 redo log 和 bin log 继续提交。

为何会使用两阶段提交呢?

咱们能够用反证法。

若是redo log在前 bin log在后,在redo log写完以后宕机,那么重启以后主库能够根据 redo log 进行数据恢复,可是这时候由于 bin log 是没有写的,因此若是使用 bin log 进行备份,那么备库会少了这一个事务。

若是bin log在前 redo log在后,在bin log写完以后宕机,那么就会致使后面使用 bin log作备份的时候多出这个事务。

因此若是不使用 两阶段提交 ,那么就会出现 bin log备份出来的数据库和原库的数据不一致

因此redo log 结合着上面的两阶段提交就解决了,crash-safe 能力和 原库备库一致性。

为何要存在两个日志,只要一个不行吗

你可能会想,若是不引入两个日志就没有必要进行 两阶段提交 了,这样岂不是快哉?!

咱们能够继续利用反证法去证实。

若是咱们只有 redo log,你知道 redo log 大小是固定且是能够被覆盖的,因此若是用来作数据备份是不能够的,由于它仅仅会记录当前内存中数据页的状况。并且 redo log是 innodb 层面的,它不是 数据库层面的,若是当你使用的另一个数据库不是 以 innodb 做为存储引擎的话,是根本进行不了同步的

若是咱们只有 bin log,咱们知道bin log是数据行级别且记录的是逻辑日志,因此是没有“数据页恢复”的功能的

因此,这里咱们仍是须要使用 redo log 和 bin log。

redo log的 prepare 和 commit 两个状态的存在乎义是什么

这里咱们还得引出一个点,咱们上面提到了 redo log 的落盘是在事务执行过程当中。那么,redo log究竟具体在何时会进行日志的持久化呢?

具体有三种

  1. redo log buffer占用的空间要达到 innodb_log_buffer_size一半的时候,会有后台线程主动将日志写盘。注意,因为这个事务并无提交,因此这个写盘动做只是write,而没有调用fsync,也就是只留在了文件系统的page cache
  2. 后台线程会作每秒的轮询将 redo log buffer write到文件系统的page cache并调用 fsync 进行写盘
  3. 并行的事务提交的时候,顺带将这个事务的redo log buffer持久化到磁盘

这里咱们提到了两个很关键的词:redo log bufferpage cache

那么这个redo log buffer 和 page cache 又是干什么的呢?这里咱们须要将一下 redo log 的三种形态。以下图

你能够想一下,一个事务会有多个 DML 语句,而每次 DML 语句都进行写盘会进行大量的系统调用致使资源浪费和时间浪费,因此每次 DML 语句的时候只是会将 日志先缓存到内存中的 redo log buffer 中去,而最终调用 commit 的时候会将 redo log buffer 中的内容写入磁盘。

而 page cache 的存在是为了加快 fsync 系统调用的速度,咱们知道每次事务 commit 的时候都会进行两次 fsync 调用(双一配置),而主要的 redo log 通常会提前进行 write 到文件系统缓冲中,因此这样会加快写盘速度。

在这里,我放了一张加入“缓存”的DML更新流程的图。

其中 bin log cache你也能够理解为缓存,并且由于bin log是逻辑日志,因此一个事务的bin log是不能被拆开的,因此咱们的 bin log cache 是存放在每一个线程的空间里的,相互独立。以下图

注意:redo log在最后只是 write 进行了写入文件系统的 page cache 中是由于这个时候已经能够保证 crash-safe能力了,就不须要再额外进行写盘操做了,若是不理解能够结合上面的两阶段提交规则去理解。

因此这里 redo log 的两种状态其实也是两阶段提交的重要组成部分,咱们能够知道,在bin log未写盘以前 redo log会先进行写盘,可是此次写盘的状态还只是 prepare 状态,只有在bin log 写完以后 才会最终将状态变为 commit,而且这里再也不进行写盘操做,而是经过后面进行写盘的时候顺便写入。

为何 WAL 技术能提升数据库性能

咱们这个时候可能还会有一个疑惑,在“双一配置”下,每次事务的提交都须要进行两次 fsync 系统调用,这样对于数据库的压力会是很大的。

咱们知道 WAL 技术能提升数据库性能的一个缘由是——日志文件是顺序写的,磁盘的顺序写要比随机写快不少。可是对于每次事务进行两次系统调用这点,WAL 有没有作什么优化呢?

答案是有的,试想一下,若是存在多个事务并发的状况下,此时会出现多个事务的 redo log buffer都已经写好,这时候 InnoDB 会使用 LSN(log sequence number)日志逻辑序列号,LSN 是单调递增的,用来对应 redo log的写入点,每次写入 length 长度的 redo log,LSN 就会增长 length。

好比此时有,三个并发事务trx1,trx2,trx3。咱们能够看到 trx1 是第一个到达的,而 trx1 要进行写盘的时候已经有三个事务在队列中了,因此此时 trx1 去写盘的时候带的 LSN 就会变成 200,那么此时进行写盘,就会将trx1,trx2,trx3都写入磁盘中了,这里仅进行了一次系统调用。

因此这里 WAL 技术会对一些须要系统调用写盘的地方进行一些优化,尽可能减小IO。对于这个问题就能够总结为两点:

  1. 经过日志的顺序写提升磁盘效率
  2. 经过组提交减小系统调用

总结

这里咱们主要介绍了在 MySQL 中 一条 DML 语句是如何执行的,redo log 、bin log又是如何和 DML 语句、事务联系在一块儿的。其中还介绍了 redo log的三种形态和两种提交状态,bin log的线程cache,LSN组提交实现等。

总的来讲就是 MySQL 在进行 DML 语句的时候会先写日志缓存(为了事务多个 DML 语句而很少次进行写盘操做),等到事务提交的时候会进行日志的真正落盘(“双一配置”),其中还使用了两阶段提交加上redo log的两种提交状态来实现 crash-safe能力 和 redo log,bin log 的同步

相关文章
相关标签/搜索