MySQL三大日志

技术加油站大约 11 分钟

  1. redo log :重做日志
  2. undo log :回滚日志
  3. binlog :归档日志

binlog

  1. 由server层记录,采用任何存储引擎都会记录binlog日志(开启的情况下)
  2. 属于逻辑日志,可以简单的理解为记录的是sql语句
  3. 采用文件追加的方式进行记录,所以是顺序io
  4. 文件大小由max_binlog_size参数控制,超过这个大小后则会记录到一个新的binlog文件中
  5. 主要用来进行主从复制
  6. 对innodb而言,只有在事务提交的时候才会记录binlog(事务过程中不会产生binlog),那binlog何时刷盘呢?
  7. 让mysql集群拥有保持一致性的能力

binlog刷盘策略

刷盘策略由sync_binlog参数控制,参数值为0-N

  • 0: 系统自行判断何时写入磁盘
  • 1: 每次事务提交都进行刷盘
  • N: 每N个事务刷一次盘

默认为1,为了进一步提高性能减少io次数,可以考虑设置大一点的数值

binlog格式

  1. STATEMENT:基于SQL语句的复制( statement-based replication, SBR ),就可以理解为记录的是会造成数据修改的SQL语句
  • 优点:记录的是sql语句,不是数据的变化,相对ROW来说减少了binlog日志量,减少了io
  • 缺点:可能造成数据的不一致,比如now()函数,这样的sql在从库执行后,由于网络延迟等原因,得出的时间就不一致
  1. ROW: 基于行的复制(row-based replication,RBR),记录的不是sql语句,而是具体的值
  • 优缺点基本statement相反
  1. MIXED: 记录的内容是前两者的混合,MySQL会判断这条SQL语句是否可能引起数据不一致,如果是,就用row格式,否则就用statement格式。

redo log

redo log就是重做日志,主要用在当mysql挂了或者mysql服务器宕机后,重启mysql的时候用来恢复数据,已保证数据的完整性和一致性

让mysql有了崩溃恢复的能力(binlog让mysql集群有了保持一致性的能力)

保证数据完整一致性,干嘛不直接刷盘呀?

为了不丢失数据并能保证一致性,最简单的做法是在每次事务提交的时候,将该事务涉及修改的数据页全部刷新到磁盘中,但这样会存在严重的性能问题

我们知道mysql的最新io操作单位是页,每页的大小是16kb.

回想一下我们平时执行的delete、update、insert,涉及到数据肯定不是挨着,都会离散存储在多个页上的

那么一个事务可能会修改多个页的数据,而每页修改的数量量可能又很小,比如几个字节

如果每次提交事务都进行刷盘,那基本都是随机IO,并且还可能把本身没有修改的数据再次刷盘,大量浪费了io资源

所有有没有一个办法能够既能达到一致性,又能减少随机IO提供性能的方法呢?

答案就是 redo log

redo log怎么做到的

  1. redo log记录的是对数据页做了什么什么样的变更,记录的是对数据页的更改
  2. 在事务进行中也会产生redo log(binlog是在提交的时候才会产生)
  3. 采用了WAL技术(日志先行),也就是同一个事务的数据,对redo log的数据必须先落盘,具体指:
  • 比如数据页落盘到了事务100,那么redo log的落盘也至少在事务100这个位置(这里需强调是指落盘)
  • 比如binlog落盘到了事务100,那么redo log的落盘也至少在事务100这个位置(这里需强调是指落盘)

redo log buffer的刷盘策略

InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。

另外可通过 innodb_flush_log_at_trx_commit 参数控制刷盘行为,共三个参数值:

  • 0: 设置为 0 的时候,表示每次事务提交时不进行刷盘操作

    在mysql挂了或者服务器宕机的时候就可能丢失1s内的数据

  • 1:设置为 1 的时候,表示每次事务提交时都将进行刷盘操作(默认值)

    由于进行了刷盘操作,所以不会丢失数据

  • 2:设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache

    由于主动写入了page cache,所以在mysql挂了但服务器没有宕机的时候是不会丢失数据的,只有在服务器宕机的情况下才会丢失数据。虽然操作系统会定期对page cache进行刷盘

注意:即使某个事务还没有提交,InnoDB存储引擎也有可能会把重做日志缓冲中的内容刷新到重做日志文件中。

redo log文件组

可以设置多个redo log日志文件。

这些redo log日志文件循环使用,

可以想象成一个圆

write pos: 表示redo log记录到了什么位置(比如事务100的位置)

check point: 表示现在数据页落盘了什么位置(比如事务60的位置)

红色的位置:是redo log落盘的位置,比如事务80的位置

write pos到check point之间:表示redo log空闲的地方,可以写入数据

check point到write pos之间:表示redo log尚未落盘或者没有全部落盘的地方(比如上图红色的位置)

当write pos追上check point:说明redo log日志已经写满了,这个实话必须推动check point向前移动(也就是必须把buffer pool中的全部或部分数据进行落盘,以达到推动check point的目的,所以总体redo log的大小不能设置太小,要不然就可能会频繁出现write pos追上check point的情况,进入被迫对数据页进行落盘,就达不到减少随机io的目的了)

当check point追上write pos:说明mysql没有多少事务,导致write pos往前推动不迅速

注意:这里说的事务100、60位置的描述不严谨,具体应该是LSN(这里暂时不深究,不影响理解工作原理)

崩溃时候一般从check point的位置开始读取redo log进行数据恢复,但也有另外一种情况就是:

数据页落盘到了事务70位置,但更新check point时候就崩溃了,这样check point的还停留在事务60位置,那么这个时候就是从事务70的时位置开始恢复数据

通过redo log非常巧妙的降低了数据页的随机io,同时又保证了数据库不丢失数据又保证了一致性

redo log的两阶段提交

个人感觉可以理解为写入redo log中的数据有两个状态:prepare 和 commit 状态

在事务执行过程中写入的redo log是 prepare 状态

在提交事务的时候,会把对应的redo log设置为 commit 状态

mysql在利用redo log恢复数据的时候,如果发现处于prepare状态,同时在binlog中又没有找到对应的记录,就会认为这个事务没有提交,就会回滚事务。否则就执行事务进行重放

上面提到了,事务提交的时候会写binlog(不一定落盘),同时也会更改redo log的状态为commit状态,那么会不会发生一些情况导致两者不一致呢?

杂项

enter description here
enter description here

我们知道mysql有个针对页的页缓冲(buffer pool),也就是更新表数据的时候,其实是更新的是buffer pool里面的数据页的数据

还有一个针对redo log的重做日志缓冲(redo log buffer),每次对数据页进行更改后,会记录'对数据页做了很了什么更改'到redo log buffer里

随机io转变为顺序io

为了减少随机io,在每次更新数据的时候,mysql实际上更新的是buffer pool,并不会对立马对buffer pool中的数据进行刷盘

而为了能保证数据完整一致性,在每次更新buffer pool的同时也会更新 redo log buffer。?

某种程度上,可以说随机io转变为了顺序io

综上,由于redo log是顺序写入,所以某种程度上可以说把随机io转变为了顺序io,提高了性能的同时也保证了数据的完整一致性。

相关参数

innodb_log_buffer_size:控制重做日志缓冲的大小,默认是16M

innodb_log_file_size:控制重做日志文件的大小

innodb_log_files_in_group:控制重做日志文件的数

============================================

binlog和redo log的区别

  1. 实现位置不一样:binlog是在server层实现,和具体的存储引擎无关。任何存储引擎都可以开启binlog,而redo log是在innodb存储引擎中实现

  2. 作用不一样:binlog可以实现mysql集群的数据一致性,主要用来主从复制。而redo log使得mysql有了崩溃恢复的能力,保证了数据的完整性和一致性(crash-safe)

  3. 写入时间不一样:binlog是在事务提交的时候写入,而redo log是在事务执行过程中也会写入

  4. 记录的内容不一样:binlog可以简单的理解为记录的是sql语句,而binlog记录的是对数据也的修改,比如对哪个数据页做了什么样的修改

  5. binlog日志是在事务提交后写入的,每个事务只会产生一个日志,而redo log是在事务进行中不断被写入的,每个事务会产生多个日志,事务间的redo log是交叉存放的

undo log

回滚日志,记录的是事务修改数据前的状态,保证了事务的原子性,比如执行的insert,那么对应一个delete,执行的delete,对应一个insert。

可以简单的理解为记录的undo log会形成一个链表,每个undo log就是链表中的一个节点,这个节点中有几个关键信息:值、事务id,下一个节点的指针

undo log在配合readview,可以实现mvcc的功能

MVCC 的实现依赖于:隐藏字段、Read View、undo log(不是redo log)

事务的ACID

A: undo log C: undo log、redo log、binlog I: undo log、mvcc、readview、间隙锁、行数 D: redo log、binlog

杂项

  1. force log at commit : 在事务提交时,必须将该事物的所有日志写入重做日志文件中进行持久化3. 数据库运行时不需要对redo log进行读操作,而undo log是需要进行随机读写的

  2. 重做日志缓存首先写入的是文件系统缓存,因此为了确保重做日志进入磁盘,必须进行一次fsync操作。而fsync的性能取决于磁盘的性能,因此磁盘的性能就决定了事务提交的性能。

  3. 事务进行过程中就记录redo log

  4. 通过force log at commit机制来实现事务提交前将日志缓存中与当前事务相关的所有日志落地到磁盘文件,这样即便发送了宕机等情况,mysql在重启的时候也能将数据恢复。

  5. 事务进行过程中也会产生undo log,当回滚事务的时候,mysql会根据undo log来执行与之前操作相反的操作来进行事务的回滚。

redo log和undo log的区别

  1. redo存放在重做日志文件中,而undo保存在共享表空间中。

  2. 数据库允许过程中,只会对redo log进行写操作,不会进行读取操作,但会对undo log进行随机读写。

对数据库进行修改的时候不但会产生redo log,还会产生相应的undo log。执行undo的时候也会产生redo log

数据库执行回滚的时候,做的是与之前相反的操作。之前insert回滚时delete,之前delete回滚时insert,之前update回滚时执行相反的update.

扩展阅读:

  1. https://mp.weixin.qq.com/s/KUv-Mx-FRrfQpx09Xv5Itgopen in new window
  2. https://mp.weixin.qq.com/s/lnLSOPQkjTkr957ds_4ZPAopen in new window









  • 随机毒鸡汤:不要随便吃垃圾食品,否则越吃越上瘾。