目录标题
- 前言
- 为什么需要redo log
- redo log中的WAL(先写日志,再写磁盘)
- 重要参数innodb_flush_log_at_trx_commit
- 如何选择
- redo log记录形式
- 日志块(log block)
- redo log的格式
- redo log何时刷入磁盘
- 正常关闭服务器时
- 事务提交时(innodb_flush_log_at_trx_commit = 1 )
- 后台线程输入(innodb_flush_log_at_trx_commit = 0 or innodb_flush_log_at_trx_commit = 2 )
- redo log buffer 空间不足时
- 触发checkpoint规则
- 一条更新语句的执行步骤
- Mysql的两阶段提交
前言
mysql 有多种日志,每种日志都有其特定的用途。 redolog 是 mysql 存储引擎为 innodb 时,特有的日志。 innodb 是 mysql 最常用的存储引擎,它的事务的持久性就是通过 redolog保证的。
我们都知道,事务的四大特性里面有一个是 持久性 ,具体来说就是只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来的状态 。
那么 mysql是如何保证一致性的呢?最简单的做法是在每次事务提交的时候,将该事务涉及修改的数据页全部刷新到磁盘中。但是这么做会有严重的性能问题,主要体现在两个方面:
因为 Innodb 是以 页 为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,这个时候将完整的数据页刷到磁盘的话,太浪费资源了!
一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机IO写入性能太差!
因此 mysql 设计了 redo log , 具体来说就是只记录事务对数据页做了哪些修改,这样就能完美地解决性能问题了(相对而言文件更小并且是顺序IO)。
为什么需要redo log
innodb是以页为单位来管理存储空间的,任何的增删改差操作最终都会操作完整的一个页,会将整个页加载到buffer pool中,然后对需要修改的记录进行修改,修改完毕不会立即刷新到磁盘,因为此时的刷新是一个随机io,而且仅仅修改了一条记录,刷新一个完整的数据页的话过于浪费了。但是如果不立即刷新的话,数据此时还在内存中,如果此时发生系统崩溃最终数据会丢失的,因此权衡利弊,引入了redo log,也就是说,修改完后,不立即刷新,而是记录一条日志,日志内容就是记录哪个页面,多少偏移量,什么数据发生了什么变更。这样即使系统崩溃,再恢复后,也可以根据redo日志进行数据恢复。另外,redo log是循环写入固定的文件,是顺序写入磁盘的。
redo log中的WAL(先写日志,再写磁盘)
redo log 包括两部分:一个是内存中的日志缓冲( redo log buffer
),另一个是磁盘上的日志文件( redo log file
)。 mysql 每执行一条 DML 语句,先将记录写入 redo log buffer
,后续某个时间点再一次性将多个操作记录写到 redo log file
。这种 先写日志,再写磁盘 的技术就是 MySQL里经常说到的 WAL(Write-Ahead Logging) 技术。
在计算机操作系统中,用户空间( user space )下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统内核空间( kernel space )缓冲区( OS Buffer
)。因此, redo log buffer
写入redo log file
实际上是先写入 OS Buffer
,然后再通过系统调用fsync()
将其刷到redo log file
中,过程如下:
重要参数innodb_flush_log_at_trx_commit
mysql 支持三种将 redo log buffer 写入 redo log file 的时机,可以通过 innodb_flush_log_at_trx_commit
参数配置,各参数值含义如下:
innodb_flush_log_at_trx_commit=0 (延迟写)
事务提交时不会将 redo log buffer 中日志写入到 os buffer ,而是InnoDB存储引擎后台有一个线程,每隔1秒,就会把redo log buffer中的内容写入到os buffer 。并调用 fsync() 写入到 redo log file 中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。
innodb_flush_log_at_trx_commit=1(实时写,实时刷)
表示事务每次提交都会将 redo log buffer 中的日志写入 os buffer 并调用 fsync() 刷到 redo log file 中。只要事务提交成功,那么 redo log 就必然在磁盘里了。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。
innodb_flush_log_at_trx_commit=2(实时写,延迟刷)
表示提交事务的时候,都仅写入到 os buffer ,然后是每秒调用 fsync() 将 os buffer 中的日志写入到 redo log file 。
总结:
0表示每秒将"log buffer"同步到"os buffer"且从"os buffer"刷到磁盘日志文件中。
1表示每事务提交都将"log buffer"同步到"os buffer"且从"os buffer"刷到磁盘日志文件中。
2表示每事务提交都将"log buffer"同步到"os buffer"但每秒才从"os buffer"刷到磁盘日志文件中。
如何选择
对数据丢失很敏感,设置为1,保证写到磁盘上。但性能较差。
对数据不太敏感,设置为0或2,性能较好。但可能会丢失1秒的数据。
redo log记录形式
redo Log以顺序的方式写入文件,当全部文件写满的时候则回到第一个文件相应的起始位置进行覆盖写(但在做redo checkpoint时,也会更新第一个日志文件的头部checkpoint标记,所以严格来讲也不算顺序写),在InnoDB内部,逻辑上Redo Log被看作一个文件,对应一个space id (InnoDB通过space的概念来组织物理存储,包括不同的表,数据字典,redo,undo等)。如下图:
同时我们很容易得知, 在innodb中,既有 redo log 需要刷盘,还有 数据页 也需要刷盘, redo log 存在的意义主要就是降低对 数据页 刷盘的要求 。
在上图中,write pos
表示 redo log 当前记录的 LSN(逻辑序列号)位置, check point
表示 数据页更改记录刷盘后对应 redo log 所处的 LSN (逻辑序列号)位置。
write pos
到 check point
之间的部分是 redo log 空着的部分,用于记录新的记录;check point
到 write pos
之间是 redo log 待落盘的数据页更改记录。当write pos
追上check point
时,会先推动 check point 向前移动,空出位置再记录新的日志。
启动 innodb 的时候,不管上次是正常关闭还是异常关闭,总是会进行恢复操作。因为 redo log 记录的是数据页的物理变化,因此恢复的时候速度比逻辑日志(如 binlog )要快很多。 重启 innodb 时,首先会检查磁盘中数据页的 LSN ,如果数据页的 LSN 小于日志中的 LSN ,则会从 checkpoint 开始恢复。 还有一种情况,在宕机前正处于checkpoint 的刷盘过程,且数据页的刷盘进度超过了日志页的刷盘进度,此时会出现数据页中记录的 LSN 大于日志中的 LSN ,这时超出日志进度的部分将不会重做,因为这本身就表示已经做过的事情,无需再重做。
日志块(log block)
innodb存储引擎中,redo log以块为单位进行存储的,每个块占512字节,这称为redo log block。所以不管是log buffer中还是os buffer中以及redo log file on disk中,都是这样以512字节的块存储的。
每个redo log block由3部分组成:日志块头、日志块尾和日志主体。其中日志块头占用12字节,日志块尾占用8字节,所以每个redo log block的日志主体部分只有512-12-8=492字节。
因为redo log记录的是数据页的变化,当一个数据页产生的变化需要使用超过492字节()的redo log来记录,那么就会使用多个redo log block来记录该数据页的变化。
redo log的格式
因为innodb存储引擎存储数据的单元是页(和SQL Server中一样),所以redo log也是基于页的格式来记录的。默认情况下,innodb的页大小是16KB(由 innodb_page_size 变量控制),一个页内可以存放非常多的log block(每个512字节),而log block中记录的又是数据页的变化。
其中log block中492字节的部分是log body,该log body的格式分为4部分:
redo_log_type:占用1个字节,表示redo log的日志类型。
space:表示表空间的ID,采用压缩的方式后,占用的空间可能小于4字节。
page_no:表示页的偏移量,同样是压缩过的。
redo_log_body表示每个重做日志的数据部分,恢复时会调用相应的函数进行解析。例如insert语句和delete语句写入redo log的内容是不一样的。
如下图,分别是insert和delete大致的记录方式。
redo log何时刷入磁盘
正常关闭服务器时
事务提交时(innodb_flush_log_at_trx_commit = 1 )
在事务提交时,为了保证持久性,会把log buffer中的日志全部刷到磁盘。注意,这时候,除了本事务的,可能还会刷入其它事务的日志。
后台线程输入(innodb_flush_log_at_trx_commit = 0 or innodb_flush_log_at_trx_commit = 2 )
innodb_flush_log_at_trx_commit = 0:每秒写入 os buffer 并调用 fsync() 写入到 redo log file 中
innodb_flush_log_at_trx_commit = 2:每秒调用 fsync() 将 os buffer 中的日志写入到 redo log file 中
redo log buffer 空间不足时
redo log buffer 的大小是有限的,如果不停的往这个有限大小的 log buffer 里塞入日志,很快它就会被填满。如果当前写入 log buffer 的redo 日志量已经占满了 log buffer 总容量的大约一半左右,就需要把这些日志刷新到磁盘上。
触发checkpoint规则
重做日志缓存、重做日志文件都是以块(block)的方式进行保存的,称之为重做日志块(redo log block),块的大小是固定的512字节。我们的redo log它是固定大小的,可以看作是一个逻辑上的 log group,由一定数量的log block 组成。
它的写入方式是从头到尾开始写,写到末尾又回到开头循环写。
其中有两个标记位置:
write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。
checkpoint是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到磁盘。
当write_pos追上checkpoint时,表示redo log日志已经写满。这时候就不能接着往里写数据了,需要执行checkpoint规则腾出可写空间。
所谓的checkpoint规则,就是checkpoint触发后,将buffer中日志页都刷到磁盘。
一条更新语句的执行步骤
- 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
- 执行器拿到引擎给的行数据,把这个值加上1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare状态。然后告知执行器执行完成了,随时可以提交事务。 【写入 redo log(处于 prepare 阶段)】
- 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。【写 binlog】
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。【提交事务(处于 commit 状态)】
Mysql的两阶段提交
上面的流程采用了两阶段提交,那为什么要采用两阶段提交呢?是为了让 binlog 和 redo log 之间的逻辑一致。
我们假设一下上面的 update 语句在执行的每个时刻,MySQL 崩溃了,看一下两个日志间的逻辑是如何保持一致的。
假设在步骤3前,MySQL崩溃重启,那么事务提交失败,不会影响数据。虽然更新了内存,但崩溃后,内存会丢失。
假设在步骤3完成后崩溃,此时已经写入 redo log 了,重启后,发现 redo log 处于 prepare 阶段,就不恢复。
假设在步骤4完成后崩溃,此时已经写入 binlog 了,重启后,发现 binlog 已经写入了,就把对应的 redo log 改为 commit 状态。
这样就能保证 redo log 和 binlog 的逻辑一致性。
两阶段提交是跨系统维持数据逻辑一致性时常用的一个方案。
参考:
https://blog.csdn.net/weixin_40449300/article/details/117927295
https://www.cnblogs.com/liang24/p/14089065.html
https://blog.csdn.net/weixin_43213517/article/details/117457184
https://www.zhihu.com/question/486105337/answer/2538190061