binlog

binlog 是什么?

前文介绍了 undo log,它主要用于事务回滚,记录每一个增删改操作的信息,使可以恢复到执行操作之前的状态

前文介绍了 redo log,它主要用于保证持久性,记录每一个增删改操作在某表空间某页做了某修改,使系统崩溃后可以恢复未刷新到磁盘中的数据

本文要介绍的 binlog 和 redo log 有一点相似的地方,但不多!在每一个增删改操作后,server 层会生成一条 binlog,一个事务中可能会生成若干条 binlog,事务提交后统一写入 binglog 文件中

binlog 文件是记录了所有数据库表结构变更 (create、alter、drop table) 和表数据修改 (insert、update、delete) 的日志,不会记录查询类操作,如:selectshow

但并不是没有导致数据库发生变化就不需要记录到 binlog,假设表中没有a = 2的记录,但执行update t set a = 1 where a = 2操作,虽然没有导致数据库变化,但依然会记录到 binlog 中

binlog 的格式

一共有三种二进制记录的方式:

MySQL 5.1.5 之前 binlog 的格式只有 Statement 模式,MySQL 5.1.5 开始支持 Row 格式的 binlog,从 MySQL 5.1.8 开始支持 MIXED 模式

MySQL 5.7.7 之前默认使用 Statemen 模式,MySQL 5.7.7 开始默认使用 Row 模式

binlog 的作用

redo log 文件组是循环写入,只记录内存中未刷盘的脏页,当脏页刷盘后,redo log 对应的数据就可以覆盖,称作一次 checkpoint

而 binlog 文件记录了修改的所有数据,不会覆盖,所以 binlog 文件只会越来越大,由多个以文件名.00000*命名的文件组成,可以通过max_binlog_size参数设置每个文件的最大容量

即然 binlog 记录了全量数据,那么就可以用来备份恢复主从复制

问题一:redo log 可以用来备份恢复或者主从复制吗?

问题二:binlog 可以替代 redo log 用作断电后恢复吗?

关于该问题更详细的解释可见 为什么 redo log 具有 crash-safe 的能力,是 binlog 无法替代的?

问题三:binlog 和 redo log 的区别?

MySQL 主从复制

MySQL 的主从复制依赖于 binlog 日志,因为它记录了数据库中所有的变化,并以二进制的形式存储在磁盘上,复制的过程就是将 binlog 中数据从主库传输到从库

总的来说,复制有三个步骤:

1

在第一步中,多个事务可能是交替执行,但每个事务生成的 binlog 日志是按照顺序写入 binlog 文件中

在第二步中,主库会创建一个 binlog dump 线程负责发送 binlog 给从库,从库会创建一个 I/O 线程连接 binlog dump 线程,负责接收 binlog 并写入 relay log 中。I/O 线程并不会一直对事件轮询,当该线程追赶上主库后就进入睡眠状态,直到主库发送信号量通知它有新的事件产生时才会被唤醒 (注:主从之间通信基于事件驱动)

在第三步中,从库会创建一个 SQL 线程专门负责读 relay log 并重放数据,最终实现主从数据一致性

注意:主库会为每一个从库的 I/O 线程启动一个 binlog dump 线程,所以并不是从库越多越好,越多的从库代表着主库要创建越多的 binlog dump 线程,一般 1 个主库配 2 ~ 3 个从库

主从复制一般主要有三种模型:

问题:主从同步延迟分析

首先主从复制都是单线程,即主库的 binlog dump 线程、从库的 I/O 线程和 SQL 线程都只有一个

对于上面的前两步都是顺序 IO,速度较快,而在第三步中 SQL 线程重放 binlog 却是随机 IO,涉及到写数据库,由于记录可能分布在不同的物理页面上,所以写数据库产生的随机 IO 效率很低

当主库并发过高,会导致 relay log 中需要重写的 binlog 就更多,然而 SQL 线程重放的效率又很慢,就会导致主从同步出现延迟

如果从库数据安全性要求不高的情况下,可以将设置参数innodb_flush_log_at_trx_commit = 0详情可见 redo log 刷盘时机

关于该问题更详细的解释可见 深入解析 Mysql 主从同步延迟原理及解决方案

binlog 刷盘时机

每个事务生成的 binlog 日志需要按照顺序写入 binlog 文件中。假设事务 1 和事务 2 分别生成了三条 binlog 日志,编号为1 2 34 5 6

2

由于从库中重放过程是单线程,而且每当执行一个新事务时,会默认提交上一个事务,所以如果按照第三种错误顺序执行,会被拆分成四个事务执行,违背了事务的 原子性,执行效果如下图所示:

3

MySQL 为每个线程分配了一块内存作为 binlog cache,可以通过参数binlog_cache_size控制每个线程 binlog cache 的大小,如果存储内容超过了参数规定的大小,就要暂停存到磁盘

在事务执行时产生的 binlog 先写入到 binlog cache 中;在事务提交时,执行器把 binlog cache 里由事务产生的所有 binlog 写入到 binlog 文件 (page cache) 中,并清空 binlog cache

4

可以看到,每个线程都有自己的 binlog cache,但最终都写到了同一个 binlog 文件中

writefsync调用的时机由参数sync_binlog控制:

sync_binlog = 0性能最好,但安全性最低;sync_binlog = 1安全性最高,但性能最低。所以在实际业务场景中,不建议设置为 0,而是设置为 100 ~ 1000 中的某个值

小结:执行一条更新操作时三种日志的处理流程

到此为止,已经介绍完了最主要的三种不同类型的日志:undo logredo log 以及 binlog,现在我们来看看执行一条update时这三种日志到底是如何处理滴!!

假设执行update t set name = 'LFool' where id = 1,根据执行计划可以判断使用的是 聚簇索引,那么具体流程如下:

主要分三个阶段:更新前记录 undo log,同时需要在 redo log 中记录 Undo 页的修改;更新时记录对应数据页的 redo log;更新后记录该操作的 binlog

Undo 页和数据页的刷盘时机由 Buffer Pool 中脏页刷盘时机 决定;redo log 和 binlog 刷盘时机由自身设置策略决定