bingoohuang / blog

write blogs with issues
MIT License
177 stars 23 forks source link

MySQL中的7种日志 #137

Open bingoohuang opened 4 years ago

bingoohuang commented 4 years ago

MySQL中有7种日志文件

  1. 重做日志(redo log)
  2. 回滚日志(undo log)
  3. 二进制日志(bin log)
  4. 错误日志(error log)
  5. 慢查询日志(slow query log)
  6. 一般查询日志(general log)
  7. 中继日志(relay log)

事务的4大特性ACID。

一个很好的事务处理系统,必须具备这些标准特性:

重做日志(redo log)

  1. 利用WAL技术(Write-Ahead-Logging, 即先写日志,后写磁盘)推迟物理数据页的刷新,从而提升数据库吞吐,有效降低了访问时延。

  2. redo log是InnoDB引擎特有的:binlog是MySQL的Service层实现的,所有的引擎都可以使用

  3. redo log提供了crash-safe的能力,即使MySQL异常重启,之前提交的记录也不会丢失。

  4. redo log是物理日志,记录的是在某一个数据页面上做了什么修改,binlog是逻辑日志,记录的是这个语句的原始逻辑

  5. redo log是固定大小的,从头开始写,写到末尾之后又回头开头循环写,binlog是可以追加写当到达一定大小后会切换到下一个不会覆盖之前的日志

  6. redo log的写入分成了 prepare 和 commit 两个阶段, 称为两阶段提交

  7. redo log并不是直接写入磁盘的,而是先写入到缓存区,我们把这个缓冲区叫做 redo日志缓冲区

    在 MySQL Server 5.7 下 redo日志缓冲区的大小默认为 1M,我们可以通过 innodb_log_buffer_size 参数来设置 redo 日志缓冲区的大小。

    缓冲区和磁盘之间的数据如何同步?

    在 MySQL配置文件中提供了 innodb_flush_log_at_trx_commit 参数,这个可以用来控制缓冲区和磁盘之间的数据如何同步:

    • 0:表示当提交事务时,并不将缓冲区的 redo 日志写入磁盘的日志文件,而是等待主线程每秒刷新。
    • 1:在事务提交时将缓冲区的 redo 日志同步写入到磁盘,保证一定会写入成功。
    • 2:在事务提交时将缓冲区的 redo 日志异步写入到磁盘,即不能完全保证 commit 时肯定会写入 redo 日志文件,只是有这个动作。

      我们使用默认值 1 就好,这样可以保证 MySQL 异常重启之后数据不丢失。

  8. InnoDb引擎会先将操作(更新)记录写到redo log中,并更新内存,此时就算更新完成了。InnoDb引擎会在空闲或适当的时候将操作记录写到磁盘上

image

image

  1. 上图中画出了序号从0-3,共4个redo log
  2. write pos为当前写入位置, check point为检查点, write pos和check point都一直向前推进;write pos和check point之间的绿色部分就可以用来写入redo log
  3. checkpoint之前的所有数据都已经刷新回磁盘,当DB crash后,通过对checkpoint之后的redo log进行恢复就可以了
  4. 如果redo log过多, 此时check point还没有推进, write pos就追上了check point, 那么就必须等待check point向前推进, 此时MySQL无法处理更新操作

image

回滚日志(undo log)

重做日志记录了事务的行为,可以很好的通过其对页进行“重做”操作。但是事务有时候还需要进行回滚操作,也就是ACID中的A(原子性),这时就需要Undo log了。因此在数据库进行修改时,InnoDB存储引擎不但会产生Redo,还会产生一定量的Undo。这样如果用户执行的事务或语句由于某种原因失败了,又或者用户一条ROLLBACK语句请求回滚,就可以利用这些Undo信息将数据库回滚到修改之前的样子。

Undo log是InnoDB MVCC事务特性的重要组成部分。当我们对记录做了变更操作时就会产生Undo记录,Undo记录默认被记录到系统表空间(ibdata)中,但从5.6开始,也可以使用独立的Undo 表空间。

Undo记录中存储的是老版本数据,当一个旧的事务需要读取数据时,为了能读取到老版本的数据,需要顺着undo链找到满足其可见性的记录。当版本链很长时,通常可以认为这是个比较耗时的操作。

undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。

undo log只将数据库逻辑地恢复到原来的样子,在回滚的时候,它实际上是做的相反的工作,比如一条INSERT ,对应一条 DELETE,对于每个UPDATE,对应一条相反的 UPDATE,将修改前的行放回去。undo日志用于事务的回滚操作进而保障了事务的原子性。

在InnoDB存储引擎中,undo存储在回滚段(Rollback Segment)中,每个回滚段记录了1024个undo log segment,而在每个undo log segment段中进行undo 页的申请,在5.6以前,Rollback Segment是在共享表空间里的,5.6.3之后,可通过 innodb_undo_tablespace设置undo存储的位置。

在InnoDB存储引擎中,undo log分为:insert undo log、update undo log

二进制日志(bin log)

MySQL的主库(Master)对数据库的任何变化(创建表,更新数据库,对行数据进行增删改),都以二进制文件的方式记录与主库的Binary Log(即binlog)日志文件中。

image 图来自1Dump Thread Enhancement On MySQL-5.7.2

  1. redolog是InnoDB特有的日志,binlog属于Server层日志
  2. 有两份日志的历史原因
    • 一开始并没有InnoDB,采用的是MyISAM,但MyISAM没有crash-safe的能力,binlog日志只能用于归档
    • InnoDB是以插件的形式引入MySQL的,为了实现crash-safe,InnoDB采用了redolog的方案
  3. binlog一开始的设计就是不支持崩溃恢复(原库)的,如果不考虑搭建从库等操作,binlog是可以关闭的(sql_log_bin)

redolog vs binlog

binlog有两种模式

  1. statement格式:SQL语句
  2. row格式:行内容(记两条,更新前和更新后),推荐
    • 日志一样的可以用于重放

基于Binlog的流式日志抽取的架构与原理

从库的IO Thread异步地同步Binlog文件并写入到本地的Replay文件。SQL Thread再抽取Replay文件中的SQL语句在从库进行执行,实现数据更新。 需要注意的是,MySQL Binlog 支持多种数据更新格式 – 包括Row,Statement,或者mix(Row和Statement的混合)。我们建议使用Row这种Binlog格式(MySQL5.7之后的默认支持版本),可以更方便更加实时的反映行级别的数据变化。

image 图来自让你的数据库流动起来 – 利用MySQL Binlog实现流式实时分析架构

我们利用一些客户端工具“佯装”成MySQL Slave,抽取出Binlog的日志文件,并把数据变化注入到实时的流式数据管道中。我们在管道后端对Binlog的变化日志,进行消费与必要的数据处理(例如利用AWS的Lambda服务实现无服务器的代码部署),同步到多种异构数据源中 – 例如 Redshift, ElasticSearch, S3 (EMR) 等等。具体的架构如下图所示。

中继日志(relay log)

image 图片来自MySQL 主从复制 完全上手指南

image 图片来自MySQL Replication

thanks

  1. MySQL学习笔记二
  2. MySQL 持久化保障机制-redo 日志
  3. 说说MySQL中的Redo log Undo log都在干啥
  4. MySQL InnoDB存储引擎事务的ACID特性
  5. MySQL -- redolog + binlog
  6. 图文并茂,带你了解SQL更新的过程
bingoohuang commented 4 years ago

redo log的参数innodb_flush_method

mysql的innodb_flush_method这个参数控制着innodb数据文件及redo log的打开、刷写模式,对于这个参数,文档上是这样描述的:

有三个值:fdatasync(默认),O_DSYNC,O_DIRECT

首先文件的写操作包括三步:open,write,flush

这三种模式写数据方式具体如下:

image

原文链接:针对innodb_flush_method参数的理解和对比测试(mycat+mysql)