MySQL事务一致性的实现方式:redo log(重做日志)
redo日志是什么:WAL(Write-ahead logging,预写式日志)
- 所有的修改都先被写入到日志中,然后再被应用到系统中。通常包含redo和undo两部分信息。
- 如果一个系统直接将变更应用到系统状态中,那么在机器掉电重启之后系统需要知道操作是成功了,还是只有部分成功或者是失败了(为了恢复状态)。如果使用了WAL,那么在重启之后系统可以通过比较日志和系统状态来决定是继续完成操作还是撤销操作。
redo日志的作用
- 物理层面看,这些日志都指明了对哪个表空间的哪个页进行了修改。
- 逻辑层面看,在系统崩溃重启时,并不能直接根据这些日志里的记载,将页面内的某个偏移量处恢复成某个数据,而是需要调用一些事先准备好的函数,执行完这些函数后才可以将页面恢复成系统崩溃前的样子。(可以简单理解为保存了参数)
- 核心点:redo日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统崩溃重启后可以把事务所做的任何修改都恢复出来。
redo日志格式
- type:该条redo日志的类型,redo日志设计大约有53种不同的类型日志。
- space ID:表空间ID。
- page number:页号。
- data:该条redo日志的具体内容。(比如:某个事务将系统表空间中的第100号页面中偏移量为1000处的那个字节的值1改成2)
redo日志的写入过程
redo log block和日志缓冲区
- InnoDB为了更好的进行系统崩溃恢复,把redo日志都放在了大小为512字节的块(block)中。
- 为了解决磁盘速度过慢的问题而引入了Buffer Pool。同理,写入redo日志时也不能直接直接写到磁盘上,实际上在服务器启动时就向操作系统申请了一大片称之为redo log buffer的连续内存空间,翻译成中文就是redo日志缓冲区,我们也可以简称为log buffer。这片内存空间被划分成若干个连续的redo log block,我们可以通过启动参数innodb_log_buffer_size来指定log buffer的大小,该启动参数的默认值为16MB。
- 向log buffer中写入redo日志的过程是顺序的,也就是先往前边的block中写,当该block的空闲空间用完之后再往下一个block中写。
redo日志刷盘时机
- log buffer空间不足时,log buffer的大小是有限的(通过系统变量innodb_log_buffer_size指定),如果不停的往这个有限大小的log buffer里塞入日志,很快它就会被填满。InnoDB认为如果当前写入log buffer的redo日志量已经占满了log buffer总容量的大约一半左右,就需要把这些日志刷新到磁盘上。
- 事务提交时,我们前边说过之所以使用redo日志主要是因为它占用的空间少,还是顺序写,在事务提交时可以不把修改过的Buffer Pool页面刷新到磁盘,但是为了保证持久性,必须要把修改这些页面对应的redo日志刷新到磁盘。
- 后台有一个线程,大约每秒都会刷新一次log buffer中的redo日志到磁盘。
- 正常关闭服务器时等等。
redo日志文件组
- MySQL的数据目录(使用SHOW VARIABLES LIKE 'datadir'查看)下默认有两个名为ib_logfile0和ib_logfile1的文件,log buffer中的日志默认情况下就是刷新到这两个磁盘文件中。如果我们对默认的redo日志文件不满意,可以通过下边几个启动参数来调节:
innodb_log_group_home_dir,该参数指定了redo日志文件所在的目录,默认值就是当前的数据目录。
innodb_log_file_size,该参数指定了每个redo日志文件的大小,默认值为48MB,
innodb_log_files_in_group,该参数指定redo日志文件的个数,默认值为2,最大值为100。
- 磁盘上的redo日志文件可以不只一个,而是以一个日志文件组的形式出现的。
- 这些文件以ib_logfile[数字](数字可以是0、1、2...)的形式进行命名。
- 在将redo日志写入日志文件组时,是从ib_logfile0开始写,如果ib_logfile0写满了,就接着ib_logfile1写,同理,ib_logfile1写满了就去写ib_logfile2,依此类推。如果写到最后一个文件该咋办?那就重新转到ib_logfile0继续写。
- 既然Redo log文件是循环写入的,在覆盖写之前,总是要保证对应的脏页已经刷到了磁盘。在非常大的负载下,为避免错误的覆盖,InnoDB 会强制的flush脏页。
redo日志文件格式
- log buffer本质上是一片连续的内存空间,被划分成了若干个512字节大小的block。将log buffer中的redo日志刷新到磁盘的本质就是把block的镜像写入日志文件中,所以redo日志文件其实也是由若干个512字节大小的block组成。
- redo日志文件组中的每个文件大小都一样,格式也一样,都是由两部分组成:前2048个字节,也就是前4个block是用来存储一些管理信息的。
- 从第2048字节往后是用来存储log buffer中的block镜像的。
Log Sequence Number
- 自系统开始运行,就不断的在修改页面,也就意味着会不断的生成redo日志。redo日志的量在不断的递增,就像人的年龄一样,自打出生起就不断递增,永远不可能缩减了。
- InnoDB为记录已经写入的redo日志量,设计了一个称之为Log Sequence Number的全局变量,翻译过来就是:日志序列号,简称LSN。规定初始的lsn值为8704(也就是一条redo日志也没写入时,LSN的值为8704)。
- redo日志都有一个唯一的LSN值与其对应,LSN值越小,说明redo日志产生的越早。
flushed_to_disk_lsn
- redo日志是首先写到log buffer中,之后才会被刷新到磁盘上的redo日志文件。InnoDB中有一个称之为buf_next_to_write的全局变量,标记当前log buffer中已经有哪些日志被刷新到磁盘中了。
- 我们前边说lsn是表示当前系统中写入的redo日志量,这包括了写到log buffer而没有刷新到磁盘的日志,相应的,InnoDB也有一个表示刷新到磁盘中的redo日志量的全局变量,称之为flushed_to_disk_lsn。
- 系统第一次启动时,该变量的值和初始的lsn值是相同的,都是8704。
查看系统中的各种LSN值
- SHOW ENGINE INNODB STATUS;(结果较多,重点关注下面几个值)
- Log sequence number:代表系统中的lsn值,也就是当前系统已经写入的redo日志量,包括写入log buffer中的日志。
- Log flushed up to:代表flushed_to_disk_lsn的值,也就是当前系统已经写入磁盘的redo日志量。
- Pages flushed up to:代表flush链表中被最早修改的那个页面对应的oldest_modification属性值。
- Last checkpoint at:当前系统的checkpoint_lsn值。
innodb_flush_log_at_trx_commit的用法
- 为了保证事务的持久性,用户线程在事务提交时需要将该事务执行过程中产生的所有redo日志都刷新到磁盘上。会很明显的降低数据库性能。如果对事务的持久性要求不是那么强烈的话,可以选择修改一个称为innodb_flush_log_at_trx_commit的系统变量的值。
- 该变量有3个可选的值:
- 0:当该系统变量值为0时,表示在事务提交时不立即向磁盘中同步redo日志,这个任务是交给后台线程做的。
这样很明显会加快请求处理速度,但是如果事务提交后服务器挂了,后台线程没有及时将redo日志刷新到磁盘,那么该事务对页面的修改会丢失。- 1:当该系统变量值为1时,表示在事务提交时需要将redo日志同步到磁盘,可以保证事务的持久性。1也是innodb_flush_log_at_trx_commit的默认值。
- 2:当该系统变量值为2时,表示在事务提交时需要将redo日志写到操作系统的缓冲区中,但并不需要保证将日志真正的刷新到磁盘。
这种情况下如果数据库挂了,操作系统没挂的话,事务的持久性还是可以保证的,但是操作系统也挂了的话,那就不能保证持久性了。
结束语
- 获取更多有价值的文章,让我们一起成为架构师!
- 关注公众号,可以让你逐步对MySQL以及并发编程有更深入的理解!
- 这个公众号,无广告!!!每日更新!!!