binlog redo undo 区别(转)

571 阅读9分钟

1. 作用

> binlog二进制日志是mysql-server层的,主要是做主从复制,时间点恢复使用\
> redo log重做日志是InnoDB存储引擎层的,用来保证事务安全\
> undo log回滚日志保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。**select如果没有特定加锁的话就是快照读,用到了undolog,而insert delete和update都是当前读,与这个日志关系不大。**

relog

InnoDB 解决事务每次提交,都需要刷新到磁盘。redo日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统奔溃重启后可以把事务所做的任何修改都恢复出来。 好处如下:

  • 刷新一个完整的数据页太浪费了

    有时候我们仅仅修改了某个页面中的一个字节,但是我们知道在InnoDB中是以页为单位来进行磁盘IO的,也就是说我们在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘,我们又知道一个页面默认是16KB大小,只修改一个字节就要刷新16KB的数据到磁盘上显然是太浪费了。

  • 随机IO刷起来比较慢

    一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,倒霉催的是该事务修改的这些页面可能并不相邻,这就意味着在将某个事务修改的Buffer Pool中的页面刷新到磁盘时,需要进行很多的随机IO,随机IO比顺序IO要慢,尤其对于传统的机械硬盘来说。

  • redo日志占用的空间非常小

    存储表空间ID、页号、偏移量以及需要更新的值所需的存储空间是很小的,关于redo日志的格式我们稍后会详细唠叨,现在只要知道一条redo日志占用的空间不是很大就好了。

  • redo日志是顺序写入磁盘

    将修改操作改成log记录下来,这样系统恢复的时候,加载完binlog只需要重新做relog就好。

binlog

redo log和binlog区别

  • redo log是属于innoDB层面,binlog属于MySQL Server层面的,这样在数据库用别的存储引擎时可以达到一致性的要求。
  • redo log是物理日志,记录该数据页更新的内容;binlog是逻辑日志,记录的是这个更新语句的原始逻辑
  • redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。
  • binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。

一条更新语句执行的顺序

update T set c=c+1 where ID=2;

  • 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
  • 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
  • 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
  • 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
  • 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。

undolog

重做日志记录了事务的行为,可以很好地通过其对页进行“重做”操作。但是事务有时还需要进行回滚操作,这时就需要undo。因此在对数据库进行修改时, InnoDB存储引擎不但会产生redo,还会产生一定量的undo。
redo存放在重做日志文件中,与redo不同,undo存放在数据库内部的一个特殊段(segment)中,这个段称为undo段(undo segment)。undo段位于共享表空间内。
除了回滚操作,undo的另一个作用是MVCC,即在 InnoDB存储引擎中MVCC的实现是通过undo来完成。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。
undo log会产生redo log,也就是undo log的产生会伴随着 redo log的产生,这是因为undo log也需要持久性的保护

Buffer pool

当更新数据的时候,如果对应的页在Buffer Pool中,则直接更新Buffer Pool中的页即可,对应的页不在Buffer Pool中时,才会从磁盘加载对应的页到Buffer Pool,然后再更新,「此时Buffer Pool中的页和磁盘中的页数据是不一致的,被称为脏页」。这些脏页是要被刷回到磁盘中的

「这些脏页是多会刷回到磁盘中的?」 有如下几个时机

  1. Buffer Pool不够用了,要给新加载的页腾位置了,所以会利用改进的后的LRU算法,将一些脏页刷回磁盘
  2. 后台线程会在MySQL不繁忙的时候,将脏页刷到磁盘中
  3. redolog写满时(redolog的作用后面会提到
  4. 关闭时会将所有脏页刷回到磁盘

「1. 数据库支持的并发度不高」

在一些并发要求高的系统中,可以调高Buffer Pool和redo log,这样可以避免频繁的刷脏页,提高并发

「2. 事务提交很慢」

原来我负责的一个系统跑的挺正常的,直到上游系统每天2点疯狂调我接口,然后我这边都是事务方法,事务提交很慢。监控到Buffer Pool和redo log的设置都很合理,并没有太小,所以问题出在哪了?我也不知道

「后来dba排查到原因,把复制方式从半同步复制改为异步复制解决了这个问题」

「异步复制」:MySQL默认的复制即是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理,这样就会有一个问题,主如果crash掉了,此时主上已经提交的事务可能并没有传到从库上,如果此时,强行将从提升为主,可能导致新主上的数据不完整

「半同步复制」:是介于全同步复制与全异步复制之间的一种,主库只需要等待至少一个从库节点收到并且 Flush Binlog 到 Relay Log 文件即可,主库不需要等待所有从库给主库反馈。同时,这里只是一个收到的反馈,而不是已经完全完成并且提交的反馈,如此,节省了很多时间

「全同步复制」:指当主库执行完一个事务,所有的从库都执行了该事务才返回给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能必然会收到严重的影响

「3. 在一个方法中,我先插入了一条数据,然后过一会再查一遍,结果插入成功,却没有查出来」

这个比较容易排查,如果系统中采用了数据库的读写分离时,写插入的是主库,读的却是从库,binlog同步比较慢时,就会出现这种情况,此时只需要让这个方法强制走主库即可

配置

为什么要弄2种日志呢?其实这都是由历史原因决定的

MySQL刚开始用binlog实现归档的功能,但是binlog没有crash-safe的能力,所以后来InnoDB引擎加了redo log来实现crash-safe。假如MySQL中只有一个InnoDB引擎,说不定就能用redo log来实现归档了,此时就可以将redo log和 binlog合并到一块了

这两种日志的区别如下:

  1. redo log是InnoDB存储引擎特有,binglog是MySQL的server层实现的,所有引擎都可以使用
  2. redo log是物理日志,记录的是数据页上的修改。binlog是逻辑日志,记录的是语句的原始逻辑,如给id=2的这一行的c字段加1
  3. redo log是固定空间,循环写。binlog是追加写,当binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志 「我们可以通过设置sync_binlog来决定binlog的刷盘策略」
sync_binlog值作用
0不立即刷盘,将binlog写入os buffer,由操作系统决定何时刷盘 ,有可能会丢失多个事务的数据
1将binlog写入os buffer,每n个事务提交后,将os buffer的数据刷盘

一般情况下将sync_binlog的值设为1即可

「接下来我们详细聊聊,redolog是如何避免数据丢失的」

事务未提交,MySQL宕机,这种情况Buffer Pool中的数据丢失,并且redo log buffer中的日志也会丢失,不会影响数据

提交事务成功,redo log buffer中的数据没有刷到磁盘,此时会导致事务提交的数据丢失。

「鉴于这种情况,我们可以通过设置innodb_flush_log_at_trx_commit来决定redo log的刷盘策略」

查看innodb_flush_log_at_trx_commit的配置

SHOW GLOBAL VARIABLES LIKE 'innodb_flush_log_at_trx_commit'
innodb_flush_log_at_trx_commit值作用
0提交事务时,不会将redo log buffer中的数据写入os buffer,而是每秒写入os buffer并刷到磁盘
1提交事务时,必须把redo log从内存刷入到磁盘文件中
2提交事务时,将rodo log写入os buffer中,默认每隔1s将os buffer中的数据刷入磁盘

应为0和2都可能会造成事务更新丢失,所以一般系统中innodb_flush_log_at_trx_commit的值都为1,你可以看看你们的系统用的哪个值?