redo日志是什么?
假设我们在事务提交后发生了某个故障,导致内存中的数据全部失效了,对于刚刚事务在数据库中的操作所做的更改也就跟着丢失了,所以需要这个持久性。一个简单的做法是在事务提交完成之前,把该事务修改的所有页面都刷新到磁盘。不过这个粗暴的做法存在两个问题:
- 刷新一个完整的数据页太浪费了。对于仅仅修改页面中的一个字节,但是由于innodb是以页为单位来进行磁盘I/O的,也就是说在该事务提交时不得不讲一个完整的页面从内存中刷新到磁盘。一个页面的默认大小是16KB,因为修改了一个字节就要刷新16KB的数据到磁盘上,显然太浪费。
- 随机I/O刷新起来比较慢。一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,"倒霉催" 的是该事务修改的这些页面可能并不相邻。这就意味着在将来某个事务修改的Buffer Pool中的页面刷新到磁盘时,需要进行很多的随机I/O。随机I/O比顺序I/O要慢,尤其是对于传统的机械硬盘。
再次回到我们的初心:我们只是想让已经提交了的事务对数据库中的数据所做的修改能永久生效,即使后来系统崩溃,在重启之后也能把这种修改恢复过来。所以没必要在每次提交事务时就把该事务在内存中修改过的全部页面刷新到磁盘,只需要把修改的内容记录一下就好。比如,某个事务将系统表空间100号页面中偏移量为1000处的那个字节的值从1改成2,我们只需要进行如下记录:
将系统表空间100号页面中偏移量为1000处的那个字节的值从1更新为2。
这样在事务提交时,就会把上述内容刷新到磁盘中。即使之后系统崩溃了,重启之后只要按照上述内容所记录的步骤重新更新一下数据页,那么该事务对数据库中所做的修改就可以被恢复出来,这样也就意味着满足持久性的要求。
因为在系统因崩溃而重启时需要按照上述内容所记录的步骤重新更新数据页,所以上述内容也称为重做日志(redo log)。我们可以中西结合,将它称为redo 日志。相较于在事务提交时将所有修改过的内存中的页面刷新到磁盘中,只将该事务执行过程中产生的redo日志刷新到磁盘具有下面这些好处。
- redo日志占用的空间非常小:在存储表空间id、页号、偏移量以及需要更新的值时,需要的存储空间很小。
- redo日志是顺序写入磁盘的:在执行事务的过程中,每执行一条语句,就可能产生若干条redo日志,这些日志是按照产生的顺序写入磁盘的,也就是顺序io。
redo日志文件
redo日志刷盘时机
MTR运行过程中产生的一组redo日志在MTR结束时会被复制到log buffer中。可是这些日志总在内存里待着也不是个办法,在一些情况下它们会被刷新到磁盘中。来看下面这些例子。
- log buffer空间不足时
log buffer的大小是有限的(通过系统变量innodb_log_buffer_size指定),如果不停地向这个有限大小的log buffer中塞入日志,很快就会将它填满。设计InnoDB的大叔认为,如果当前写入log buffer的redo日志量已经占满了log buffer总容量的50%左右,就需要把这些日志刷新到磁盘中。 - 事务提交时。
之所以提出redo日志的概念,主要是因为它占用的空间少,而且可以将其顺序写入磁盘。引入redo日志后,虽然在事务提交时可以不把修改过的buffer pool页面立即刷新到磁盘,但是为了保证持久性,必须要把页面修改时所对应的redo日志刷新到磁盘;否则系统崩溃后,无法将该事务对页面所做的修改恢复过来。 - 将某个脏页刷新到磁盘前,会保证先将脏页对应的redo日志刷新到磁盘(再一次强调,redo日志是顺序刷新的,所以在将某个脏页对应的redo日志从redo log buffer刷新到磁盘时,也会保证将在其之前产生的redo日志也刷新到磁盘)
- 后台有一个线程,大约以每秒一次的频率将log buffer中的redo日志刷新到磁盘。
- 正常关闭服务器时。
- 做checkpoint时。
redo日志文件组
MYSQL的数据目录(show variables like 'datadir')下默认有名为ib_logfile0和ib_logfile1的两个文件,log buffer中的日志在默认情况下就是刷新到这两个磁盘文件中。如果对默认的redo日志文件不满意,可以通过下面几个启动选项来调节。
- innodb_log_group_home_dir:指定了redo日志文件所在的目录,默认值就是当前的数据目录。
- innodb_log_file_size:指定了每个redo日志文件的大小,在Mysql5.7.22版本中的默认值为48MB。
- innodb_log_files_in_group:指定了redo日志文件的个数,默认值是2,最大值是100。
从上面的描述中可以看到,磁盘上的redo日志文件不止一个,而是以一个日志文件组的形式出现的。这些文件以 'ib_logfile[数字]'(数字可以是0、1、2...)的形式进行命名。在将redo日志写入日志文件组时,从ib_logfile0开始写起;如果ib_fle0写满了,就接着ib_logfile1写;同理,ib_logfile1写满了就去写ib_logfile2;依此类推。如果写到最后一个文件时发现写满了,该咋办?那就重新转到ib_logfile0继续写。整个过程如下所示。
小贴士: 如果采用循环的方式向redo日志文件组中写数据的话,那岂不是要 “追尾”?也就是后写入的redo日志将覆盖前面写的redo日志。当然可能这样!所以设计InnoDB的大叔提出了checkpoint的概念。
redo日志文件格式
innodb_flush_log_at_trx_commit的用法
前面讲到,为了保证事务的持久性,用户线程在事务提交时需要将该事务执行过程中产生的所有redo日志都刷新到磁盘中。这一条要求太狠了,会明显地降低数据库性能。如果对事务的持久性要求不那么强烈,可选择修改一个名为innodb_flush_log_at_trx_commit的系统变量的值。该变量有三个可选的值。
- 0: 当系统变量的值为0时,表示在事务提交时不立即向磁盘同步redo日志,这个任务交给后台线程来处理;这样会明显加快请求处理速度。但是,如果事务提交后服务器挂了,后台线程没有及时将redo日志刷新到磁盘,那么该事务对页面的修改会丢失。
- 1: 当系统变量的值为1时,表示在事务提交时需要将redo日志同步到磁盘;这可以保证事务的持久性。innodb_flush_log_at_trx_commit的默认值也是1.
- 2: 当系统变量的值为2时,表示在事务提交时需要将redo日志写到操作系统的缓冲区中,但并不需要保证将日志真正地刷新到磁盘。在这种情况下,如果数据库挂了而操作系统没有挂,事务的持久性还是可以保证的。但是如果操作系统也挂了,那就不能保证持久性了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。