草庐IT

Mysql两大日志之binlog和redo log

尖笔尖 2023-07-09 原文

本文内容基本摘自于 《MySQL技术内幕》一书,但是在该书中对于这两大日志的内容比较零散,分布于多个章节,本文将与之相关的内容整合起来,方便学习。

目录​​​​​​​

binlog 日志

binlog 参数配置

主从复制

redo log 日志

redo log 参数配置

为什么需要 redo log

binlog 和 redo log 区别

两阶段提交


binlog 日志


二进制日志(binary log ),记录对 Mysql 数据库执行的所有 更改操作,包括 表结构的变更表数据的修改 等,像 select 这种查询是不会记录 binlog 日志的。

binlog 日志采用 追加写 的方式写文件,一个文件写满后新写一个文件,仅在 事务提交前 进行一次写入。

生成的 binlog 日志文件可以用于 备份恢复主从复制 以及 数据审计 等用途。

binlog 参数配置

binlog 日志默认是没有启动的,可以通过配置参数 log-binlog_bin 来开启,开启 binlog 日志后,还有许多相关的参数可以做配置,这里捡几个重要的看下:

  • max_binlog_size
  • binlog_cache_size
  • sync_binlog
  • binlog_format

max_binlog_size: 指定单个日志文件的最大值,达到阈值后,会生成一个新的日志文件,后缀名 +1,并记录到 .index 文件,形如 mysql-bin.000001、mysql-bin.000002 ....

binlog_cache_size: 日志缓存区大小。在事务未提交前,所有的 binlog 日志都会记录到内存缓存,等到事务提交的时候再写入日志文件,该参数即是设置该缓存区的大小。此外,日志缓存是基于会话的,也就是说 每个线程都有一块 binlog_cache_size 大小的内存缓存,因此,该值不宜设置太大;另一方面,如果某事务占用缓存超过设置值,就需要将日志写入临时文件,因此,该值也不能设置太小。那多大比较合适?可以通过 SHOW GLOBAL STATUS 查看 binlog_cache_disk_use (使用临时文件写日志次数)来判定,如果该值很大,说明缓存区过小,需要经常写临时文件,此时需要适当调大该参数。

sync_binlog: 同步磁盘策略。我们平时写文件调用的 write() 函数其实并没有真正的将内容写到磁盘,而是写到文件系统的 page cache 里,真正将内容同步到磁盘的是 fsync() 函数。sync_binlog 就是用来设置 fsync() 函数的执行时机的:

  • sync_binlog = 0 : 不调用 fsync() 函数,由操作系统决定何时调用;
  • sync_binlog = 1 : 每个事务调用 write() 后立马执行 fsync() 函数;
  • sync_binlog = N (N>1) : 累计 N 个事务调用 write() 后,执行 fsync() 函数;

binlog_format: 日志格式,有三种格式选择:

  • STATEMENT: 基于 SQL 语句记录(可以理解为记录的就是更新数据的 sql 语句)。这种格式的日志存在一个问题,动态函数在重放时可能产生不一致的结果,比如记录日志时调用一个动态函数生成 uuid,那使用该 binlog 日志重放时可能会得到一个不同的 uuid 值。
  • ROW: 记录数据行更改后的值,这样就避免了日志重放时因为动态函数带来的不一致的问题,但是这种格式的日志文件 相较 STATEMENT 要大很多,因为它记录的是每一行数据的变化值,比如执行一个 update 语句更新了 1w 条数据,采用 STATEMENT 格式就是记录这个 update 语句即可,而 ROW 则要记录这 1w 条数据更新后的值。
  • MIXED: 包含以上两个格式,默认采用 STATEMENT 记录,而对于使用 STATEMENT 记录可能产生问题的 SQL 语句则使用 ROW 记录。

无论采用哪种格式,它记录的都是 关于一个事务的具体操作内容,因此 binlog 日志也被称为 逻辑日志

主从复制

主从复制是 mysql 提供的一种 高可用高性能 的解决方案。具体来说,在主从复制架构下,如果主库发生意外,可以切换到从库继续提供服务(高可用);此外,主从库可以实现读写分离,主库可接收读写请求,从库只提供读请求能力,可以大大提高数据库整体的查询能力(高性能)。

主从复制原理很简单,包含以下三个步骤:

1、主库记录二进制日志文件;

2、从库将主库的日志文件复制到自身的中继日志(relay log);

3、从库对中继日志进行重放。

复制具体过程涉及三个线程操作:

1、主库的 log dump 线程 负责将二进制文件发送到从库;

2、从库的 I/O 线程 负责读取二进制文件并保存到从库的 relay log 中;

3、从库的 SQL 线程 负责读取 relay log 在本地进行重放。


redo log 日志


Innodb 基于磁盘存储,同时按 的方式来管理记录,一个页的大小默认为 16KB。如果每次查询或修改都要按页和磁盘进行 IO 交互会严重影响数据库的性能,因此引入了 内存缓存

有了内存缓存,在对数据进行查询时,先查缓存,如果数据存在直接返回,如果不存在则去磁盘读取并将读取到的页放到缓存池中,然后再返回数据;对数据的修改也是 先修改缓存池的页,而后异步的将页刷新回磁盘。但是异步刷新磁盘也带来一个新问题:在刷新磁盘前如果意外宕机,重启后内存数据已经没有了,就会导致数据丢失。

为了避免数据丢失,Innodb 采用了 Write Ahead Log(WAL) 策略,即 当事务提交时,先写日志,再修改页,当发生意外宕机时,可以通过日志来恢复数据,这个日志就叫做 redo log,它保证了事务的 持久性

区别于 binlog 日志,redo log 是 Innodb 引擎独有的日志模块,它只记录有关 Innodb 引擎的事务日志,记录内容为 对数据页的物理操作(比如 偏移量 500,写 ‘jianbijian’)。

redo log 参数配置

  • innodb_log_file_size
  • innodb_log_files_in_group
  • innodb_mirrored_log_groups
  • innodb_flush_log_at_trx_commit

每个 Innodb 引擎都 至少有 1 个 重做日志文件组,而 每个重做日志组下又至少有 2 个重做日志文件;参数 innodb_mirrored_log_groupsinnodb_log_files_in_group 分别用来设定 重做文件组的个数 和 每个组内文件的个数,日志组中每个日志文件大小一致,并以循环写入的方式运行。

innodb_log_file_size: 设定每个重做日志文件的大小,这个参数很重要。首先它不能设置的太小,因为重做日志文件是 循环写入 的,如果设置的太小会导致频繁的 async/sync checkpoint,当然也不能设置的超大,这样做数据恢复的时候耗时就很长。

async/sync checkpoint: 重做日志不可用时,需要强制将一些页刷新回磁盘。

重做日志是对日志组内的几个日志文件 循环写入 的(比如有两个重做日志文件 logfile1 和 logfile2,先写 logfile1, logfile1 写满后开始写 logfile2, logfile2 写满后又重新开始写 logfile1),将日志组内的 N 个文件组合起来想象成一个圆,此时总的文件大小 total_redo_log_file_size = N * innodb_log_file_size ,有两个指针,一个表示 redo log 新增时的写入指针 redo_lsn,一个表示异步刷新回磁盘最新页后的记录指针 checkpoint_lsn,再定义一个变量 checkpoint_age = redo_lsn - checkpoint_lsn, 正常情况下都有 0 <= checkpoint_age < total_redo_log_file_size,而如果事务比较频繁,innodb_log_file_size 又比较小的,就可能出现 checkpoint_age 的大小达到 total_redo_log_file_size,即 redo_lsn 绕了一圈追上了 checkpoint_lsn,这时不能让 redo_lsn 直接覆写 checkpoint_lsn 位置的值,因为 checkpoint_lsn 位置的记录还没同步到磁盘,即 重做日志不可用

innodb 其实不会等到 redo_lsn 追上 checkpoint_lsn,而是定义了两个水位线 async_water_marksync_water_mark

async_water_mark = 0.75 * total_redo_log_file_size

sync_water_mark= 0.9 * total_redo_log_file_size

遵循以下规则刷新脏页:

1、checkpoint_age < async_water_mark 时,不需要刷新页回磁盘,直接追加 redo log 日志即可;

2、async_water_mark < checkpoint_age < sync_water_mark 时触发 Async Flush 将脏页刷新回磁盘,直到满足 checkpoint_age < async_water_mark;

3、sync_water_mark < checkpoint_age 时触发 Sync Flush 将脏页刷新回磁盘,直到满足 checkpoint_age < async_water_mark

需要注意的是,在 Innodb 1.2.x 之前,Async Flush 会阻塞发现问题的用户线程, Sync Flush 则会阻塞所有线程;但在 Mysql 5.6 之后,刷新脏页由单独的 Page cleaner thread 完成,都不再阻塞用户线程。

innodb_flush_log_at_trx_commit: 提交事务时的重做日志同步磁盘策略。

redo log 也有对应的缓存 redo log buffer,写日志时先写 redo log buffer,然后再按一定条件 顺序地 写入日志文件;再考虑到文件系统的缓存,一条 redo log 日志从生成到同步磁盘的路径为:

redo log buffer --> page cache --> redo log file;

innodb_flush_log_at_trx_commit 有 0,1,2 三个有效值:

  • 0:提交事务时,不同步重做日志到磁盘,即日志仅记录在 redo log buffer 中;
  • 1:提交事务时,将缓存同步到磁盘;
  • 2:提交事务时,将缓存写到 page cache,但不会同步到磁盘,即没有 fsync() 的调用。

可以看到,如果想要确保事务的持久性,必须将该参数设置为 1;

设置为 0 的时候,redo log buffer 中的内容要等到后台主线程每秒的任务才会刷新到磁盘,如果中途 mysql 实例宕机,可能丢失 1 秒的日志;

设置为 2 的时候也是等待主线程每秒的任务刷新磁盘,但是在事务提交时,已经将日志缓存写到了操作系统的 page cache,所以只要操作系统不重启,内容也不会丢失。

为什么需要 redo log

我们已经知道 redo log 的目的是防止数据丢失,保证事务的持久性。但是下面两个问题还是值得思考的:

1、在事务提交时,要先同步重做日志到磁盘再修改数据页,为什么不直接将数据页同步到磁盘?

如果去掉 redo log,在页数据发生变更后直接将页同步到磁盘当然也可以保证事务的持久性,但是效率极低。默认情况下,一个页的大小为16KB,可以记录多条记录,而一个变更可能就涉及到一条或几条记录,它就要将记录所在的整个页同步到磁盘,属实浪费;其次,将页同步到磁盘涉及对 I/O 的 随机写 操作,效率低下。重做日志是对日志文件的追加操作,属于 顺序写,顺序写毫无疑问要比随机写快;此外,即使事务还没有提交,mysql 后台主线程每秒都会将重做日志同步到磁盘,因此,即使是很大的事务提交时间也很短。

2、binlog 日志也是事务提交前的日志,为啥不直接用 binlog 日志做恢复?

这个问题网上有诸多答案,归纳几点:

  1. 从日志格式来看,STATEMENT 格式的日志因为动态函数的原因,肯定不能用作数据恢复;
  2. 从恢复效率来看,首先,binlog 日志是逻辑日志,而 redo log 日志是物理日志,恢复更快;其次,binlog 日志是追加写文件,而 redo log 日志是循环写的,在脏页刷新回磁盘后即可被覆盖,因此在做数据恢复时,redo log 记录的内容本身就会比 binlog 更少。

binlog 和 redo log 区别

binlog

redo log

适用对象不同

mysql server 层

Innodb 存储引擎层

写入方式不同

追加写,一个文件满了写新文件

循环写固定文件

文件格式不同

逻辑日志,一个事务具体操作内容

物理日志,页的修改情况

写入磁盘时间不同

提交事务前一次写入

在事务进行中有后台线程不断同步

用途不同

主从复制、数据备份

数据恢复

两阶段提交


在主从复制模式下,会开启 binlog 日志。此时,主库提交事务时,既要写二进制日志,还要写重做日志,mysql 需要保证两个操作的原子性。假如它们不满足原子性,比如先写完二进制日志后,如果 mysql 实例发生宕机,重启后,由于 redo log 日志没有记录的原因,在做数据恢复后主库将丢失这个数据更新,而从库却根据 binlog 日志进行了重放,最终导致主从不一致。

mysql 结合 内部 XA 事务 两阶段提交方案 来实现这两个操作的原子性,看看具体流程:

1、prepare 阶段:事务提交时,先写 redo log,同时记录 XA 事务的 ID (XID),标记为 prepare 状态;

2、写入 binlog 日志,同时记录 XID

3、commit 阶段:再次写 redo log,标记为 commit 状态。

我们看看它是如果通过以上步骤实现原子性的:

在 mysql 使用 redo log 做数据恢复时,如果发现 redo log 处于 commit 状态,则表示 binlog 一定落盘了,可以直接恢复;

如果发现 redo log 处于 prepare 状态,就要根据 XID 和 binlog 日志来判断:

  • 如果在 binlog 找不到这个 XID,说明还没有同步 binlog,回滚事务;
  • 反之说明两个日志都已同步成功,提交事务。

有关Mysql两大日志之binlog和redo log的更多相关文章

  1. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  2. ruby-on-rails - 无法安装 mysql2 0.3.14 gem - 2

    我看到其他人也遇到过类似的问题,但没有一个解决方案对我有用。0.3.14gem与其他gem文件一起存在。我已经完全按照此处指示完成了所有操作:https://github.com/brianmario/mysql2.我仍然得到以下信息。我不知道为什么安装程序指示它找不到include目录,因为我已经检查过它存在。thread.h文件存在,但不在ruby​​目录中。相反,它在这里:C:\RailsInstaller\DevKit\lib\perl5\5.8\msys\CORE\我正在运行Windows7并尝试在Aptana3中构建我的Rails项目。我的Ruby是1.9.3。$gemin

  3. ruby - Sinatra 中的全局救援和日志记录异常 - 2

    如何在出现异常时指定全局救援,如果您将Sinatra用于API或应用程序,您将如何处理日志记录? 最佳答案 404可以在not_found方法的帮助下处理,例如:not_founddo'Sitedoesnotexist.'end500s可以通过调用带有block的错误方法来处理,例如:errordo"Applicationerror.Plstrylater."end错误的详细信息可以通过request.env中的sinatra.error访问,如下所示:errordo'Anerroroccured:'+request.env['si

  4. ruby - 如何使用 ruby​​ mysql2 执行事务 - 2

    我已经开始使用mysql2gem。我试图弄清楚一些基本的事情——其中之一是如何明确地执行事务(对于批处理操作,比如多个INSERT/UPDATE查询)。在旧的ruby-mysql中,这是我的方法:client=Mysql.real_connect(...)inserts=["INSERTINTO...","UPDATE..WHEREid=..",#etc]client.autocommit(false)inserts.eachdo|ins|beginclient.query(ins)rescue#handleerrorsorabortentirelyendendclient.commi

  5. ruby-on-rails - 使用 Ruby 标准 Logger 每天只创建一个日志 - 2

    我正在使用ruby​​标准记录器,我想要每天轮换一次,所以在我的代码中我有:Logger.new("#{$ROOT_PATH}/log/errors.log",'daily')它运行完美,但它创建了两个文件errors.log.20130217和errors.log.20130217.1。如何强制它每天只创建一个文件? 最佳答案 您的代码对于长时间运行的应用程序是正确的。发生的事情是您在给定的一天多次运行代码。第一次运行时,Ruby会创建一个日志文件“errors.log”。当日期改变时,Ruby将文件重命名为“errors.log

  6. ruby - Cucumber/Savon 省略或删除日志输出 - 2

    在运行Cucumber测试时,我得到(除了测试结果)大量调试/日志相关的输出形式:D,[2013-03-06T12:21:38.911829#49031]DEBUG--:SOAPrequest:D,[2013-03-06T12:21:38.911919#49031]DEBUG--:Pragma:no-cache,SOAPAction:"",Content-Type:text/xml;charset=UTF-8,Content-Length:1592W,[2013-03-06T12:21:38.912360#49031]WARN--:HTTPIexecutesHTTPPOSTusingt

  7. ruby-on-rails - faraday如何设置日志级别 - 2

    我最近将我的http客户端切换到faraday,一切都按预期工作。我有以下代码来创建连接:@connection=Faraday.new(:url=>base_url)do|faraday|faraday.useCustim::Middlewarefaraday.request:url_encoded#form-encodePOSTparamsfaraday.request:jsonfaraday.response:json,:content_type=>/\bjson$/faraday.response:loggerfaraday.adapterFaraday.default_ada

  8. 网站日志分析软件--让网站日志分析工作变得更简单 - 2

    网站的日志分析,是seo优化不可忽视的一门功课,但网站越大,每天产生的日志就越大,大站一天都可以产生几个G的网站日志,如果光靠肉眼去分析,那可能看到猴年马月都看不完,因此借助网站日志分析工具去分析网站日志,那将会使网站日志分析工作变得更简单。下面推荐两款网站日志分析软件。第一款:逆火网站日志分析器逆火网站日志分析器是一款功能全面的网站服务器日志分析软件。通过分析网站的日志文件,不仅能够精准的知道网站的访问量、网站的访问来源,网站的广告点击,访客的地区统计,搜索引擎关键字查询等,还能够一次性分析多个网站的日志文件,让你轻松管理网站。逆火网站日志分析器下载地址:https://pan.baidu.

  9. ruby-on-rails - 当我通过 rvm 使用 rails3 时,如何在 ubuntu 上安装 mysql2 gem? - 2

    我正在尝试绕过rails配置这个极其复杂的迷宫。到目前为止,我设法在ubuntu上设置了rvm(出于某种原因,ruby在ubuntu存储库中已经过时了)。我设法建立了一个Rails项目。我希望我的测试项目使用mysql而不是mysqlite。当我尝试“rakedb:migrate”时,出现错误:“!!!缺少mysql2gem。将其添加到您的Gemfile:gem'mysql2'”当我尝试“geminstallmysql”时,出现错误,告诉我需要为安装命令提供参数。但是,参数列表很大,我不知道该选择哪些。如何通过在ubuntu上运行的rvm和mysql获取rails3?谢谢。

  10. ruby - 如何更改 Sinatra 中的日志级别 - 2

    我正在使用此代码在我的Sinatra应用程序中启用日志记录:log_file=File.new('my_log_file.log',"a")$stdout.reopen(log_file)$stderr.reopen(log_file)$stdout.sync=true$stderr.sync=true实际的日志记录是使用:logger.debug("Startingcall.Params=#{params.inspect}")事实证明,只有INFO或更高级别的日志消息被记录,而DEBUG消息没有被记录。我正在寻找一种将日志级别设置为DEBUG的方法。 最佳

随机推荐