log日志

redo log(重做日志):

https://www.cnblogs.com/hapjin/archive/2019/09/28/11521506.html

https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html

redo log又称重做日志文件,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。在实例和介质失败(media failure)时,redo log文件就能派上用场,如数据库掉电,InnoDB存储引擎会使用redo log恢复到掉电前的时刻,以此来保证数据的完整性。用于保证事务的完整性。日志文件名如ib_logfile0,固定大小,循环写入,具有crash safe的能力。

由参数innodb_log_file_size = 512M 和innodb_log_files_in_group = 2控制,redo log file的大小对innodb的性能影响非常大,设置的太大,恢复的时候就会时间较长,设置的太小,就会导致在写redo log的时候循环切换redo log file。

-rw-r----- 1 mysql mysql 512M Apr 13 13:13 ib_logfile0

-rw-r----- 1 mysql mysql 512M Apr 13 13:08 ib_logfile1

MySQL 的设计者就用了类似酒店掌柜粉板+账本的思路来提升更新效率。而粉板和账本配合的整个过程,其实就是 MySQL 里经常说到的 WAL 技术(Hbase、oracle等数据库皆是如此),WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,(其实日志也是写磁盘,但是是追加写,顺序IO要比随机IO快得多。redo log 主要节省的是随机写磁盘的 IO 消耗(转成顺序写,同时还有一个组提交机制,降低IOPS),而 change buffer 主要节省的则是随机读磁盘的 IO 消耗。)也就是先写粉板,等不忙的时候再写账本。

具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。


log buffer-------->os buffer---------->log files,3个部分都算是redo log,对应于下面3个取值。

redo log 可能存在的三种状态

1、存在 redo log buffer 中,物理上是在 MySQL用户进程中,user buffer

2、写到磁盘 (write),os buffer,但是没有持久化(fsync),属于系统调用,物理上是在文件系统的 page cache 里面;

3、持久化到磁盘,对应的是 hard disk。

日志写到 redo log buffer 是很快的,wirte 到 page cache 也差不多,但是持久化到磁盘的速度就慢多了。

为了控制 redo log 的写入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,它有三种可能取值:

1、设置为 0 的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中 ;

2、设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘;

3、设置为 2 的时候,表示每次事务提交时都只是把 redo log 写到 page cache。

InnoDB 有一个后台线程,每隔 1 秒,由参数innodb_flush_log_at_timeout控制。就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。

注意,事务执行中间过程的 redo log 也是直接写在 redo log buffer 中的,这些 redo log 也会被后台线程一起持久化到磁盘。也就是说,一个没有提交的事务的 redo log,也是可能已经持久化到磁盘的。

实际上,除了后台线程每秒一次的轮询操作外,还有两种场景会让一个没有提交的事务的 redo log 写入到磁盘中。

一种是,redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动写盘。注意,由于这个事务并没有提交,所以这个写盘动作只是 write,而没有调用 fsync,也就是只留在了文件系统的 page cache。

另一种是,并行的事务提交的时候,顺带将这个事务的 redo log buffer 持久化到磁盘。假设一个事务 A 执行到一半,已经写了一些 redo log 到 buffer 中,这时候有另外一个线程的事务 B 提交,如果 innodb_flush_log_at_trx_commit 设置的是 1,那么按照这个参数的逻辑,事务 B 要把 redo log buffer 里的日志全部持久化到磁盘。这时候,就会带上事务 A 在 redo log buffer 里的日志一起持久化到磁盘。

这里需要说明的是,我们介绍两阶段提交的时候说过,时序上 redo log 先 prepare, 再写 binlog,最后再把 redo log commit。

如果把 innodb_flush_log_at_trx_commit 设置成 1,那么 redo log 在 prepare 阶段就要持久化一次,因为有一个崩溃恢复逻辑是要依赖于 prepare 的 redo log,再加上 binlog 来恢复的。

每秒一次后台轮询刷盘,再加上崩溃恢复这个逻辑,InnoDB 就认为 redo log 在 commit 的时候就不需要 fsync 了,只会 write 到文件系统的 page cache 中就够了。

通常我们说 MySQL 的“双 1”配置,指的就是 sync_binlog innodb_flush_log_at_trx_commit 都设置成 1。也就是说,一个事务完整提交前,需要等待两次刷盘,一次是 redo log(prepare 阶段),一次是 binlog。但是因为组提交机制,不会太影响IO,为了数据安全还是建议设置为1。


内存数据页磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。redo log会引起脏页问题

何时会刷脏页??

1、InnoDB 的 redo log 写满了。阻塞更新,应尽量避免。

2、系统内存不足。InnoDB 用缓冲池(buffer pool)管理内存,会优先把数据缓存在内存,而内存脏页太多会影响性能,需要控制脏页比例。innodb_io_capacity 建议你设置成磁盘的 IOPS。如果设置过小,会使mysql不能完全使用io的性能,尤其是SSD的时候。

3、系统“空闲”的时候。

4、MySQL 正常关闭的情况。

InnoDB 的刷盘速度就是要参考这两个因素:一个是脏页比例,一个是 redo log 写盘速度。

innodb_max_dirty_pages_pct 脏页比例上线,默认75%。

innodb_flush_neighbors ,开启会连坐隔壁的数据页也被刷,可以减少随机IO,磁盘较好时不需要开启,mysql 8.0默认关闭。

InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写。

有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。要理解 crash-safe 这个概念,可以想想我们前面赊账记录的例子。只要赊账记录记在了粉板上或写在了账本上,之后即使掌柜忘记了,比如突然停业几天,恢复生意后依然可以通过账本和粉板上的数据明确赊账账目。

undo日志:

undo log有两个作用:提供回滚和多个行版本控制(MVCC)

undo log和redo log记录物理日志不一样,它是逻辑日志可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。有时候应用到行版本控制的时候,也是通过undo log来实现的:当读取的某一行被其他事务锁定时,它可以从undo log中分析出该行记录以前的数据是什么,从而提供该行版本信息,让用户实现非锁定一致性读取。

undo log是采用段(segment)的方式来记录的,每个undo操作在记录的时候占用一个undo log segment另外,undo log也会产生redo log,因为undo log也要实现持久性保护。在以前老版本,只支持1个rollback segment,这样就只能记录1024个undo log segment。后来MySQL5.5可以支持128个rollback segment,即支持128*1024个undo操作,还可以通过变量innodb_undo_logs(5.6版本以前该变量是innodb_rollback_segments)自定义多少个rollback segment,默认值为128。

undo log默认存放在共享表空间中。即ibdata1文件如果开启了innodb_file_per_table,将放在每个表的.ibd(表的数据文件)中。在MySQL5.6中,undo的存放位置还可以通过变量innodb_undo_directory来自定义存放目录,默认值为"."表示datadir。默认rollback segment全部写在一个文件中,但可以通过设置变量 innodb_undo_tablespaces 平均分配到多少个文件中。该变量默认值为0,即全部写入一个表空间文件。该变量为静态变量,只能在数据库示例停止状态下修改,如写入配置文件或启动时带上对应参数。但是innodb存储引擎在启动过程中提示,不建议修改为非0的值。

当事务提交的时候,innodb不会立即删除undo log,因为后续还可能会用到undo log,如隔离级别为repeatable read时,事务读取的都是开启事务时的最新提交行版本,只要该事务不结束,该行版本就不能删除,即undo log不能删除。

但是在事务提交的时候,会将该事务对应的undo log放入到删除列表中,未来通过purge来删除。并且提交事务时,还会判断undo log分配的页是否可以重用,如果可以重用,则会分配给后面来的事务,避免为每个独立的事务分配独立的undo log页而浪费存储空间和性能。

通过undo log记录delete和update操作的结果发现:(insert操作无需分析,就是插入行而已)

delete操作实际上不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的。

update分为两种情况:update的列是否是主键列。

如果不是主键列,在undo log中直接反向记录是如何update的。即update是直接进行的。

如果是主键列,update分两部执行:先删除该行,再插入一行目标行。


binlog(归档日志):

binlog 的写入逻辑比较简单:事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。binlog 一般用来备份和恢复。一个事务的 binlog 是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。这就涉及到了 binlog cache 的保存问题。每个线程有自己 binlog cache,但是共用同一份 binlog 文件

binlog记录了对MySQL数据库执行更改的所有操作,但是不包括SELECT和SHOW(也是select其实)这类操作,因为这类操作对数据本身并没有修改。binlog可在临时库上恢复数据库到任何时间点的状态,因为它保存了所有修改的逻辑。每个线程一个,参数 binlog_cache_size 用于控制单个线程内 binlog cache 所占内存的大小。如果超过了这个参数规定的大小,就要暂存到磁盘。

1、write,指的就是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快。

2、fsync,才是将数据持久化到磁盘的操作。一般情况下,我们认为 fsync 才占磁盘的 IOPS。

write 和 fsync 的时机,是由参数 sync_binlog 控制的:

sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync;

sync_binlog=1 的时候,表示每次提交事务都会执行 fsync;

sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync(刷盘)

因此,在出现 IO 瓶颈的场景里,将 sync_binlog 设置成一个比较大的值,可以提升性能。在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成 0,比较常见的是将其设置为 100~1000 中的某个数值。

但是,将 sync_binlog 设置为 N,对应的风险是:如果主机发生异常重启,会丢失最近 N 个事务的 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 能力,即crash的一瞬间,那些没有完成的事务依然能够完成

这两种日志有以下三点不同。

1、redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。

2、redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。

3、redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

update流程:


你可能注意到了,最后三步看上去有点“绕”,将 redo log 的写入拆成了两个步骤:prepare commit,这就是"两阶段提交"。如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。

redo log 和 binlog 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。redo log 用于保证 crash-safe 能力。innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数我建议你设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。

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

我还跟你介绍了与 MySQL 日志系统密切相关的“两阶段提交”。两阶段提交是跨系统维持数据逻辑一致性时常用的一个方案,即使你不做数据库内核开发,日常开发中也有可能会用到。

事务日志redo log具有幂等性,所以多次操作得到同一结果的行为在日志中只记录一次。而二进制日志binlog不具有幂等性,多次操作会全部记录下来,在恢复的时候会多次执行二进制日志中的记录,速度就慢得多。

Mysql的IO瓶颈优化:

1、设置 binlog_group_commit_sync_delay binlog_group_commit_sync_no_delay_count 参数,减少 binlog 的写盘次数。这个方法是基于“额外的故意等待”来实现的,因此可能会增加语句的响应时间,但没有丢失数据的风险。

2、将 sync_binlog 设置为大于 1 的值(比较常见是 100~1000,默认1)。这样做的风险是,主机掉电时会丢 binlog 日志。

3、将 innodb_flush_log_at_trx_commit 设置为 2(生产默认1,redo log两阶段提交的prepare时就会持久化一次)。这样做的风险是,主机掉电的时候会丢数据。

什么时候会把线上生产库设置成“非双 1”:

1、业务高峰期。一般如果有预知的高峰期,DBA 会有预案,把主库设置成“非双 1”。

2、备库延迟,为了让备库尽快赶上主库。

3、用备份恢复主库的副本,应用 binlog 的过程,这个跟上一种场景类似。

4、批量导入数据的时候。

一般情况下,把生产库改成“非双 1”配置,是设置 innodb_flush_logs_at_trx_commit=2、sync_binlog=1000。主要就是大量操作,避免频繁IO操作,通过组的方式合并事务的IO操作。

组提交(group sync):

1、组提交的核心思想就是:一次fsync()调用,可以刷新N个事务的redo log(redo的组提交) & binlog(binlog的组提交)

2、组提交的最终目的就是为了减少IO的频繁刷盘,来提高并发性能,当然也是之后多线程复制的基础

3、组提交中:sync_binlog=1 & innodb_trx_at_commit=1 代表的不再是1个事务,而是一组事务和一个事务组的binlog

4、组提交中:binlog是顺序fsync的,事务也是按照顺序进行提交的,这都是有序的,MySQL5.7 并对这些有序的事务进行打好标记(last_committed,sequence_number )

相关问题:

问题 1:执行一个 update 语句以后,我再去执行 hexdump 命令直接查看 ibd 文件内容,为什么没有看到数据有改变呢?

回答:这可能是因为 WAL 机制的原因。update 语句执行完成后,InnoDB 只保证写完了 redo log、内存,可能还没来得及将数据写到磁盘。

问题 2:为什么 binlog cache 是每个线程自己维护的,而 redo log buffer 是全局共用的?

回答:MySQL 这么设计的主要原因是,binlog 是不能“被打断的”。一个事务的 binlog 必须连续写,因此要整个事务完成后,再一起写到文件里。

而 redo log 并没有这个要求,中间有生成的日志可以写到 redo log buffer 中。redo log buffer 中的内容还能“搭便车”,其他事务提交的时候可以被一起写到磁盘中。

问题 3:事务执行期间,还没到提交阶段,如果发生 crash 的话,redo log 肯定丢了,这会不会导致主备不一致呢?

回答:不会。因为这时候 binlog 也还在 binlog cache 里,没发给备库。crash 以后 redo log 和 binlog 都没有了,从业务角度看这个事务也没有提交,所以数据是一致的。

问题 4:如果 binlog 写完盘以后发生 crash,这时候还没给客户端答复就重启了。等客户端再重连进来,发现事务已经提交成功了,这是不是 bug?

回答:不是。

你可以设想一下更极端的情况,整个事务都提交成功了,redo log commit 完成了,备库也收到 binlog 并执行了。但是主库和客户端网络断开了,导致事务成功的包返回不回去,这时候客户端也会收到“网络断开”的异常。这种也只能算是事务成功的,不能认为是 bug。

实际上数据库的 crash-safe 保证的是:

如果客户端收到事务成功的消息,事务就一定持久化了;

如果客户端收到事务失败(比如主键冲突、回滚等)的消息,事务就一定失败了;

如果客户端收到“执行异常”的消息,应用需要重连后通过查询当前状态来继续后续的逻辑。此时数据库只需要保证内部数据和日志之间,主库和备库之间一致就可以了。


5.6 skip-counter

5.7 gtid 不能跳

slave_skip_error

你可能感兴趣的:(log日志)