innodb事务日志包括redo log和undo log。redo log是重做日志,提供前滚操作,undo log是回滚日志,提供回滚操作。
undo log不是redo log的逆向过程,其实它们都算是用来恢复的日志:
1.redo log通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
2.undo用来回滚行记录到某个版本。undo log一般是逻辑日志,根据每行记录进行记录。
二进制日志相关内容,参考:MariaDB/MySQL的二进制日志。
redo log不是二进制日志。虽然二进制日志中也记录了innodb表的很多操作,也能实现重做的功能,但是它们之间有很大区别。
redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。
在概念上,innodb通过force log at commit机制实现事务的持久性,即在事务提交的时候,必须先将该事务的所有事务日志写入到磁盘上的redo log file和undo log file中进行持久化。
为了确保每次日志都能写入到事务日志文件中,在每次将log buffer中的日志写入日志文件的过程中都会调用一次操作系统的fsync操作(即fsync()系统调用)。因为MariaDB/MySQL是工作在用户空间的,MariaDB/MySQL的log buffer处于用户空间的内存中。要写入到磁盘上的log file中(redo:ib_logfileN文件,undo:share tablespace或.ibd文件),中间还要经过操作系统内核空间的os buffer,调用fsync()的作用就是将OS buffer中的日志刷到磁盘上的log file中。
也就是说,从redo log buffer写日志到磁盘的redo log file中,过程如下:

在此处需要注意一点,一般所说的log file并不是磁盘上的物理日志文件,而是操作系统缓存中的log file,官方手册上的意思也是如此(例如:With a value of 2, the contents of the InnoDB log buffer are written to the log file after each transaction commit and the log file is flushed to disk approximately once per second)。但说实话,这不太好理解,既然都称为file了,应该已经属于物理文件了。所以在本文后续内容中都以os buffer或者file system buffer来表示官方手册中所说的Log file,然后log file则表示磁盘上的物理日志文件,即log file on disk。
另外,之所以要经过一层os buffer,是因为open日志文件的时候,open没有使用O_DIRECT标志位,该标志位意味着绕过操作系统层的os buffer,IO直写到底层存储设备。不使用该标志位意味着将日志进行缓冲,缓冲到了一定容量,或者显式fsync()才会将缓冲中的刷到存储设备。使用该标志位意味着每次都要发起系统调用。比如写abcde,不使用o_direct将只发起一次系统调用,使用o_object将发起5次系统调用。
MySQL支持用户自定义在commit时如何将log buffer中的日志刷log file中。这种控制通过变量innodb_flush_log_at_trx_commit的值来决定。该变量有3种值:0、1、2,默认为1。但注意,这个变量只是控制commit动作是否刷新log buffer到磁盘。

注意,有一个变量innodb_flush_log_at_timeout的值为1秒,该变量表示的是刷日志的频率,很多人误以为是控制innodb_flush_log_at_trx_commit值为0和2时的1秒频率,实际上并非如此。测试时将频率设置为5和设置为1,当innodb_flush_log_at_trx_commit设置为0和2的时候性能基本都是不变的。关于这个频率是控制什么的,在后面的"刷日志到磁盘的规则"中会说。
在主从复制结构中,要保证事务的持久性和一致性,需要对日志相关变量设置为如下:
上述两项变量的设置保证了:每次提交事务都写入二进制日志和事务日志,并在提交时将它们刷新到磁盘中。
选择刷日志的时间会严重影响数据修改时的性能,特别是刷到磁盘的过程。下例就测试了innodb_flush_log_at_trx_commit分别为0、1、2时的差距。

尽管设置为0和2可以大幅度提升插入性能,但是在故障的时候可能会丢失1秒钟数据,这1秒钟很可能有大量的数据,从上面的测试结果看,100W条记录也只消耗了20多秒,1秒钟大约有4W-5W条数据,尽管上述插入的数据简单,但却说明了数据丢失的大量性。更好的插入数据的做法是将值设置为1,然后修改存储过程,将每次循环都提交修改为只提交一次,这样既能保证数据的一致性,也能提升性能,修改如下:

因为redo log记录的是数据页的变化,当一个数据页产生的变化需要使用超过492字节()的redo log来记录,那么就会使用多个redo log block来记录该数据页的变化。
日志块头包含4部分:
关于log block块头的第三部分log_block_first_rec_group,因为有时候一个数据页产生的日志量超出了一个日志块,这是需要用多个日志块来记录该页的相关日志。例如,某一数据页产生了552字节的日志量,那么需要占用两个日志块,第一个日志块占用492字节,第二个日志块需要占用60个字节,那么对于第二个日志块来说,它的第一个log的开始位置就是73字节(60+12)。如果该部分的值和log_block_hdr_data_len相等,则说明该log block中没有新开始的日志块,即表示该日志块用来延续前一个日志块。
日志尾只有一个部分:log_block_trl_no,该值和块头的log_block_hdr_no相等。
上面所说的是一个日志块的内容,在redo log buffer或者redo log file on disk中,由很多log block组成。如下图:

log group表示的是redo log group,一个组内由多个大小完全相同的redo log file组成。组内redo log file的数量由变量innodb_log_files_group决定,默认值为2,即两个redo log file。这个组是一个逻辑的概念,并没有真正的文件来表示这是一个组,但是可以通过变量innodb_log_group_home_dir来定义组的目录,redo log file都放在这个目录下,默认是在datadir下。

redo log file的大小对innodb的性能影响非常大,设置的太大,恢复的时候就会时间较长,设置的太小,就会导致在写redo log的时候循环切换redo log file。
因为innodb存储引擎存储数据的单元是页(和SQL Server中一样),所以redo log也是基于页的格式来记录的。默认情况下,innodb的页大小是16KB(由innodb_page_size变量控制),一个页内可以存放非常多的log block(每个512字节),而log block中记录的又是数据页的变化。
其中log block中492字节的部分是log body,该log body的格式分为4部分:
如下图,分别是insert和delete大致的记录方式。

log buffer中未刷到磁盘的日志称为脏日志(dirty log)。
在上面的说过,默认情况下事务每次提交的时候都会刷事务日志到磁盘中,这是因为变量innodb_flush_log_at_trx_commit的值为1。但是innodb不仅仅只会在有commit动作后才会刷日志到磁盘,这只是innodb存储引擎刷日志的规则之一。
刷日志到磁盘有以下几种规则:
1.发出commit动作时。已经说明过,commit发出后是否刷日志由变量innodb_flush_log_at_trx_commit控制。
2.每秒刷一次。这个刷日志的频率由变量innodb_flush_log_at_timeout值决定,默认是1秒。要注意,这个刷日志频率和commit动作无关。
3.当log buffer中已经使用的内存超过一半时。
4.当有checkpoint时,checkpoint在一定程度上代表了刷到磁盘时日志所处的LSN位置。
内存中(buffer pool)未刷到磁盘的数据称为脏数据(dirty data)。由于数据和日志都以页的形式存在,所以脏页表示脏数据和脏日志。
上一节介绍了日志是何时刷到磁盘的,不仅仅是日志需要刷盘,脏数据页也一样需要刷盘。
在innodb中,数据刷盘的规则只有一个:checkpoint。但是触发checkpoint的情况却有几种。不管怎样,checkpoint触发后,会将buffer中脏数据页和脏日志页都刷到磁盘。
innodb存储引擎中checkpoint分为两种:
由于刷脏页需要一定的时间来完成,所以记录检查点的位置是在每次刷盘结束之后才在redo log中标记的。
MySQL停止时是否将脏数据和脏日志刷入磁盘,由变量innodb_fast_shutdown={ 0|1|2 }控制,默认值为1,即停止时只做一部分purge,忽略大多数flush操作(但至少会刷日志),在下次启动的时候再flush剩余的内容,实现fast shutdown。
LSN称为日志的逻辑序列号(log sequence number),在innodb存储引擎中,lsn占用8个字节。LSN的值会随着日志的写入而逐渐增大。
根据LSN,可以获取到几个有用的信息:
1.数据页的版本信息。
2.写入的日志总量,通过LSN开始号码和结束号码可以计算出写入的日志量。
3.可知道检查点的位置。
实际上还可以获得很多隐式的信息。
LSN不仅存在于redo log中,还存在于数据页中,在每个数据页的头部,有一个fil_page_lsn记录了当前页最终的LSN值是多少。通过数据页中的LSN值和redo log中的LSN值比较,如果页中的LSN值小于redo log中LSN值,则表示数据丢失了一部分,这时候可以通过redo log的记录来恢复到redo log中记录的LSN值时的状态。
redo log的lsn信息可以通过show engine innodb status来查看。MySQL 5.5版本的show结果中只有3条记录,没有pages flushed up to。

上图中,从上到下的横线分别代表:时间轴、buffer中数据页中记录的LSN(data_in_buffer_lsn)、磁盘中数据页中记录的LSN(data_page_on_disk_lsn)、buffer中重做日志记录的LSN(redo_log_in_buffer_lsn)、磁盘中重做日志文件中记录的LSN(redo_log_on_disk_lsn)以及检查点记录的LSN(checkpoint_lsn)。
假设在最初时(12:0:00)所有的日志页和数据页都完成了刷盘,也记录好了检查点的LSN,这时它们的LSN都是完全一致的。
假设此时开启了一个事务,并立刻执行了一个update操作,执行完成后,buffer中的数据页和redo log都记录好了更新后的LSN值,假设为110。这时候如果执行show engine innodb status查看各LSN的值,即图中①处的位置状态,结果会是:

在flush阶段写入二进制日志到内存中,但是不是写完就进入sync阶段的,而是要等待一定的时间,多积累几个事务的binlog一起进入sync阶段,等待时间由变量binlog_max_flush_queue_time决定,默认值为0表示不等待直接进入sync,设置该变量为一个大于0的值的好处是group中的事务多了,性能会好一些,但是这样会导致事务的响应时间变慢,所以建议不要修改该变量的值,除非事务量非常多并且不断的在写入和更新。
进入到sync阶段,会将binlog从内存中刷入到磁盘,刷入的数量和单独的二进制日志刷盘一样,由变量sync_binlog控制。
当有一组事务在进行commit阶段时,其他新事务可以进行flush阶段,它们本就不会相互阻塞,所以group commit会不断生效。当然,group commit的性能和队列中的事务数量有关,如果每次队列中只有1个事务,那么group commit和单独的commit没什么区别,当队列中事务越来越多时,即提交事务越多越快时,group commit的效果越明显。
到此这篇关于MySQL事务日志(redo log和undo log)的文章就介绍到这了,更多相关MySQL事务日志redo log和undo log内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!