Mysql有三种很重要的日志也是面试经常涉及到的考点,分别是 binlog 、redo log和undo log,这里面binlog 是server层实现的日志 , 而redo log 和undo log都是引擎层(innodb)实现的日志 。也正是因为它们在Mysql不同的体系结构里 , 所以他们所针对的问题也是完全不同的,下面我们就来详细讲解下这三种日志的作用以及它们之间的区别 。
一、什么是redo log (重做日志又称为前滚日志)我们知道 MySQL 数据存在磁盘中,每次读写数据需做磁盘随机IO,并发场景下性能差 。为此 MySQL 引入缓存 Buffer Pool 做优化 。其包含磁盘中部分数据页(page)的映射,来缓解数据库的磁盘压力 。
当从数据库读数据时 , 首先从缓存中读,缓存中没有,则从磁盘读后放入缓存;当向数据库写数据时,先向缓存中写,此时缓存中的数据页数据会变更,该数据页叫脏页,Buffer Pool 中修改完数据后会按照设定的策略再定期刷到磁盘中去 , 这个过程叫刷脏页 。
那么问题来了,如果 Buffer Pool 中修改的数据还没有及时的刷到磁盘,MySQL 宕机重启 , 就会导致数据丢失,无法保证事务的持久性,怎么办?
redo log 解决了这个问题 。就是说数据库在修改数据时 , 会把更新记录先写到 redo log 中,再去修改 Buffer Pool 中的数据 , 当提交事务时,调用 fsync 把 redo log 刷入磁盘 。至于缓存中更新的数据文件何时刷入磁盘,则由后台线程异步处理 。
我们先看一下Mysql数据更新的流程:
文章插图
下面详细拆解一下图中容易产生疑问的几个点:
- 首先当用户对数据进行变更操作时 , 在数据真正变更之前,存储引擎层会先将当前数据保存在undolog中形成一个历史版本,以便回滚的时候使用 。
- 然后在将脏页刷新到磁盘之前 , 存储引擎层会先将变更数据的记录写入到redo log buffer中,并且通过顺序IO刷新到磁盘当中的redolog中 , 将redolog的状态置为prepare(这里其实就相当于告诉Binlog我已经记录好数据变化了,你可以开始更新了) 。
- 于是接下来server层会进行binlog日志文件的更新,将数据变化写入到binlog中,当binlog更新完成后事务才算成功commit,并将commit这个状态写入到redolog中 。
- 最后在执行刷脏页这个操作 , 这个刷脏页的操作是随机IO 。
- 如果redo log写失败了,而binlog写成功了 。那假设内存的数据还没来得及落磁盘,机器就挂掉了 。那主从服务器的数据就不一致了 。(从服务器通过binlog得到最新的数据,而主服务器由于redo log没有记载,没法恢复数据),所以如果redo log写失败了,那我们就认为这次事务有问题,回滚 , 不再写binlog 。
- 如果redo log写成功了,而binlog写失败了 , 主从将无法同步,所以我们还是会对这次的事务进行回滚操作 , 将无效的binlog给删除(因为binlog会影响从库的数据,所以需要做删除操作)
redo log写入策略由innodb_flush_log_at_trx_commit 参数决定 。
- innodb_flush_log_at_trx_commit=1,表示在每次事务提交的时候 , 都把log buffer刷到文件系统中(os buffer)去 , 并且调用文件系统的“flush”操作将缓存刷新到磁盘上去 。这样的话,数据库对IO的要求就非常高了,如果底层的硬件提供的IOPS比较差,那么MySQL数据库的并发很快就会由于硬件IO的问题而无法提升 。
- innodb_flush_log_at_trx_commit=0 ,表示每隔一秒把log buffer刷到文件系统中(os buffer)去,并且调用文件系统的“flush”操作将缓存刷新到磁盘上去 。也就是说一秒之前的日志都保存在日志缓冲区,也就是内存上,如果机器宕掉 , 可能丢失1秒的事务数据 。
- innodb_flush_log_at_trx_commit=2,表示在每次事务提交的时候会把log buffer刷到文件系统中去 , 但并不会立即刷写到磁盘 。如果只是MySQL数据库挂掉了,由于文件系统没有问题 , 那么对应的事务数据并没有丢失 。只有在数据库所在的主机操作系统损坏或者突然掉电的情况下,数据库的事务数据可能丢失1秒之类的事务数据 。这样的好处,减少了事务数据丢失的概率,而对底层硬件的IO要求也没有那么高(log buffer写到文件系统中 , 一般只是从log buffer的内存转移的文件系统的内存缓存中,对底层IO没有压力) 。
推荐阅读