本文适合零基础小白,用来解释和记录一下为什么innodb需要 redo log。
要回答这个问题首先需要了解MySQL的数据的存储机制,下面先简单介绍一下它的存储直接,了解MySQL是如何读写数据之后,就会理解为什么需要redo log了。
首先直接说结论:MySQL中数据是以页为单位的(其大小为16kb,可以理解为MySQL读取数据的单位,是一批数据的集合),每查询一条记录都会从磁盘中加载一页数据到内存中(Buffer Pool,内存中的一个区域,稍后会详细介绍)。
众所周知,数据是存在磁盘中的,因此每次查询cpu需要去进行io操作,读取磁盘中的内容。cpu读取磁盘的速度是非常慢的(相当于内存来说),为了提升性能,MySQL在查询的时候,会将查询到的每页数据缓存到内存中(这个地方就叫做Buffer Pool),后序的查询操作都是从Buffer Pool中去查找,如果找到数据则直接返回,每有找到则去磁盘中查找数据,并返回该页数据,这样性能更高。增删改也类似,MySQL会先在Buffer Pool中更新数据,等累积到一定数量了再去磁盘中进行批量修改数据,这些数据由于没有实时同步到磁盘中,所以被称为脏数据。
这个机制虽然提高了系统的性能,但也存在一个问题,如果存在内存中的数据断电了,那么就会发生数据丢失,这对数据库来说是无法接收的,所以MySQL的innodb就搞出了一个redo log,来解决存在缓存区的数据可能丢失的情况。
当用户对数据进行增删改的时候,redo log会记录当前数据页中的修改情况,然后将其写入磁盘。当用户提交事务,修改的数据已保持到了内存中,如果此时发生宕机,我们只需要再次执行redo log 就可以恢复之前的数据,这也就是为什么redo log被称为重做日志(再次做一遍嘛),能够保证持久性的原因。
虽然大致思路看起来比较简单,但是还有一些细节上的问题需要理清楚。
1.记录日志的形式是什么样的?
当发现增删改数据的时候,redo log会记录如下形式的日志:
可以看到里面记录的是数据变化的精确信息(那个表上那个数据发生了改变),而不是SQL语句。这样做的好处是当进行重做的时候,不用执行SQL,执行效率会提升。但是这样却无法完成主从复制,因为记录的日志为当前数据库的数据,相当于相对路径,所以后面有了bin log 日志来保证完成主从复制功能。
2.频繁记录日志也会造成性能下降,如何解决
虽然以日志形式能够解决宕机数据丢失问题,但是频繁的记录日志又仿佛把问题带到了原点,还是需要频繁操作io。那么innodb又利用缓冲原理,搞了一个rodo log buffer,我先把日志缓冲起来,然后一起写入磁盘。
通过两次缓冲操作,在一定程度上解决了频繁访问io的问题,因为读取缓存的速度是要高于读取io的速度的。但是此时数据还是在内存中,还是没有持久化到内存中,还是存在数据丢失的风险的。因此,在拥有redo log日志之后,我们需要将其缓存到磁盘中,这样才能够真正保证稳定的持久化,那么什么时候进行持久化呢?
innodb提供了三种同步机制供用户选择:
此外,InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。也就是说每隔一秒就会进行一次刷盘,将redo log buffer中的内容写入到磁盘中。
当我们设置为0的时候,由于提交事务不进行刷盘,那么只能等待后台线程刷盘,所以宕机可能丢失1s数据。
当我们设置为1的时候,提交事务的时候就会刷盘,那么宕机的时候不会丢失数据。如果提交事务之前,发生了宕机,这个是不用考虑的,事务没有完成,只需回滚即可。此时需要注意一下,这里说的数据丢失的含义是:已经保存在磁盘中的数据发生了丢失。由于innodb会先把数据暂存到内存,但从用户的角度看,此时数据应该在磁盘。由于宕机,导致内存数据丢失,从用户角度看就是磁盘数据丢失,是没有持久化。如果事务没有提交,那么就没有存入磁盘,此时宕机了也就没有发生数据丢失。(以上是个人理解,可能这里优点绕)
当我们设置为2的时候,只要事务提交成功,redo log buffer中的内容只写入文件系统缓存(page cache)。如果仅仅只是MySQL挂了不会有任何数据丢失,但是宕机可能会有1秒数据的丢失。
参考文章
https://javaguide.cn/database/mysql/mysqllogs.html#%E5%88%B7%E7%9B%98%E6%97%B6%E6%9C%BA
https://zhuanlan.zhihu.com/p/213770128