MySQL实战 | 02-MySQL 如何恢复到半个月内任意一秒的状态?

原文连接:MySQL是如何作到能够恢复到任意一秒状态的?html

看到这个题目是否是以为数据库不再用担忧服务器 crash 了?mysql

那咱们须要学习为何能够这么作?以及如何作?sql

即为何能够恢复到任意时间点?如何恢复到任意时间点?数据库

为何有了 binlog 还须要 redo log?缓存

事务是如何提交的?事务提交先写 binlog 仍是 redo log?如何保证这两部分的日志作到顺序一致性?安全

为了保障主从复制安全,故障恢复是如何作的?服务器

<!--more-->性能

上一次课咱们学习了一条 select 语句的所有执行过程,那么今天咱们就从一条 update 语句开始。学习

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

其实执行流程和查询流程一致,只是最后执行器执行的是找到这条数据,并进行更新。spa

另外,更新过程还涉及到一个重要的日志模块,即 redo log(重作日志)和 binlog(归档日志)。

我我的是只听过 binlog 的。

redo log

和大多数关系型数据库同样,InnoDB 记录了对数据文件的物理更改,并保证老是日志先行

也就是所谓的 WAL(Write-Ahead Logging),即在持久化数据文件前,保证以前的 redo 日志已经写到磁盘。

MySQL 的每一次更新并无每次都写入磁盘,InnoDB 引擎会先将记录写到 redo log 里,并更新到内存中,而后再适当的时候,再把这个记录更新到磁盘。

这里有必要贴一下 InnoDB 的存储结构图:

InnoDB 物理存储结构

若是下面看的各类空间懵逼了,建议回来看一眼这个图。

redo log 是啥

当数据库对数据作修改的时候,须要把数据页从磁盘读到 buffer pool 中,而后在 buffer pool 中进行修改,那么这个时候 buffer pool 中的数据页就与磁盘上的数据页内容不一致,咱们称 buffer pool 的数据页为 dirty page 脏数据

dirty page

这里也能够看出,全部的更新操做都是如今 dirty page 中进行的。

若是这个时候发生非正常的 DB 服务重启,那么这些数据还没在内存,并无同步到磁盘文件中(注意,同步到磁盘文件是个随机 IO),也就是会发生数据丢失

若是这个时候,可以在有一个文件,当 buffer pool 中的 dirty page 变动结束后,把相应修改记录记录到这个文件(注意,记录日志是顺序 IO),那么当 DB 服务发生 crash 的状况,恢复 DB 的时候,也能够根据这个文件的记录内容,从新应用到磁盘文件,数据保持一致。

这个文件就是 redo log ,用于记录数据修改后的记录,顺序记录。

我理解的,redo log 就是存放 dirty page 的物理空间。

log 什么时候产生 & 释放?

在事务开始以后就产生 redo log,redo log 的落盘并非随着事务的提交才写入的,而是在事务的执行过程当中,便开始写入 redo log 文件中。

当对应事务的脏页写入到磁盘以后,redo log 的使命也就完成了,重作日志占用的空间就能够重用(被覆盖)。

如何写?

Redo log 文件以 ib_logfile[number] 命名,并以顺序的方式写入文件文件,写满时则回溯到第一个文件,进行覆盖写。

循环写

如图所示:

  • write pos 是当前记录的位置,一边写一边后移,写到最后一个文件末尾后就回到 0 号文件开头;
  • checkpoint 是当前要擦除的位置,也是日后推移而且循环的,擦除记录前要把记录更新到数据文件;

write pos 和 checkpoint 之间还空着的部分,能够用来记录新的操做。

若是 write pos 追上 checkpoint,表示写满,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推动一下。

Redo log 文件是循环写入的,在覆盖写以前,老是要保证对应的脏页已经刷到了磁盘

在很是大的负载下,Redo log 可能产生的速度很是快,致使频繁的刷脏操做,进而致使性能降低。

一般在未作 checkpoint 的日志超过文件总大小的 76% 以后,InnoDB 认为这多是个不安全的点,会强制的 preflush 脏页,致使大量用户线程 stall 住。

若是可预期会有这样的场景,咱们建议调大 redo log 文件的大小。能够作一次干净的 shutdown,而后修改 Redo log 配置,重启实例。

参考:
http://mysql.taobao.org/month...

相关配置

默认状况下,对应的物理文件位于数据库的 data 目录下的 ib_logfile1ib_logfile2

innodb_log_group_home_dir 指定日志文件组所在的路径,默认./ ,表示在数据库的数据目录下。
innodb_log_files_in_group 指定重作日志文件组中文件的数量,默认2
# 关于文件的大小和数量,由一下两个参数配置
innodb_log_file_size 重作日志文件的大小。
innodb_mirrored_log_groups 指定了日志镜像文件组的数量,默认1

其余

redo log 有一个缓存区 Innodb_log_buffer,默认大小为 8M,Innodb 存储引擎先将重作日志写入 innodb_log_buffer 中。

写 redo log 过程

而后会经过如下三种方式将 innodb 日志缓冲区的日志刷新到磁盘:

一、Master Thread 每秒一次执行刷新 Innodb_log_buffer 到重作日志文件;
二、每一个事务提交时会将重作日志刷新到重作日志文件;
三、当 redo log 缓存可用空间少于一半时,重作日志缓存被刷新到重作日志文件;

有了 redo log,InnoDB 就能够保证即便数据库发生异常重启,以前提交的记录都不会丢失,这个能力称为 crash-safe

CrashSafe 可以保证 MySQL 服务器宕机重启后:

  • 全部已经提交的事务的数据仍然存在。
  • 全部没有提交的事务的数据自动回滚。

binlog

如前文所讲,MySQL 总体能够分为 Server 层和引擎层。

其实,redo log 是属于引擎层的 InnoDB 所特有的日志,而 Server 层也有本身的日志,即 binlog(归档日志)。

记录了什么

逻辑格式的日志,能够简单认为就是执行过的事务中的 sql 语句。

但又不彻底是 sql 语句这么简单,而是包括了执行的 sql 语句(增删改)反向的信息。

也就意味着 delete 对应着 delete 自己和其反向的 insert;update 对应着 update 执行先后的版本的信息;insert 对应着 delete 和 insert 自己的信息。

什么时候产生 & 释放

事务提交的时候,一次性将事务中的 sql 语句按照必定的格式记录到 binlog 中。所以,对于较大事务的提交,可能会变得比较慢一些。

binlog 的默认是保持时间由参数 expire_logs_days 配置,也就是说对于非活动的日志文件,在生成时间超过配置的天数以后,会被自动删除。

区别

一、redo log 是 InnoDB 引擎特有的,binlog 是 MySQL 的 Server 层实现,全部引擎均可以使用;
二、内容不一样:redo log 是物理日志,记录的是在数据页上作了什么修改,是正在执行中的 dml 以及 ddl 语句;而 binlog 是逻辑日志,记录的是语句的原始逻辑,已经提交完毕以后的 dml 以及 ddl sql 语句,如「给 ID=2 的这一行的 c 字段加 1」;
三、写方式不一样:redo log 是循环写的,空间固定;binlog 是能够一直追加写的,一个文件写到必定大小后,会继续写下一个,以前写的文件不会被覆盖;
四、做用不一样:redo log 主要用来保证事务安全,做为异常 down 机或者介质故障后的数据恢复使用,binlog 主要用来作主从复制和即时点恢复时使用;
五、另外,二者日志产生的时间,能够释放的时间,在可释放的状况下清理机制,都是彻底不一样的。

参考:
http://www.importnew.com/2803...


数据更新事务流程

有了对这两个日志的概念性理解,咱们再来看执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程。

一、执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。若是 ID=2 这一行所在的数据页原本就在内存中,就直接返回给执行器;不然,须要先从磁盘读入内存,而后再返回。

二、执行器拿到引擎给的行数据,把这个值加上 1,好比原来是 N,如今就是 N+1,获得新的一行数据,再调用引擎接口写入这行新数据。

三、引擎将这行新数据更新到内存中,同时将这个更新操做记录到 redo log 里面,此时 redo log 处于 prepare 状态。而后告知执行器执行完成了,随时能够提交事务。

四、执行器生成这个操做的 binlog,并把 binlog 写入磁盘

五、执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改为提交(commit)状态,更新完成。

事务流程

两阶段提交

上面处理 redo log 和 binlog 看着是否是有点懵逼?

其实这就是所谓的两阶段提交,即 COMMIT 会被自动的分红 prepare 和 commit 两个阶段。

两阶段提交

MySQL 在 prepare 阶段会生成 xid,而后会在 commit 阶段写入到 binlog 中。在进行恢复时事务要提交仍是回滚,是由 Binlog 来决定的。

由上面的二阶段提交流程能够看出,经过两阶段提交方式保证了不管在任何状况下,事务要么同时存在于存储引擎和 binlog 中,要么两个里面都不存在。

这样就能够保证事务的 binlog 和 redo log 顺序一致性。一旦阶段 2 中持久化 Binlog 完成,就确保了事务的提交。

此外须要注意的是,每一个阶段都须要进行一次 fsync 操做才能保证上下两层数据的一致性。

PS:记录 Binlog 是在 InnoDB 引擎 Prepare(即 Redo Log 写入磁盘)以后,这点相当重要。
另外须要注意的一点就是,SQL 语句产生的 Redo 日志会一直刷新到磁盘(master thread 每秒 fsync redo log),而 Binlog 是事务 commit 时才刷新到磁盘,若是 binlog 太大则 commit 时会慢。

参考:
http://www.ywnds.com/?p=7892

如何恢复数据?

当须要恢复到指定的某一秒时,好比某天下午两点发现中午十二点有一次误删表,须要找回数据,那你能够这么作:

一、首先,找到最近的一次全量备份,若是你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库;

二、而后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表以前的那个时刻。

这样你的临时库就跟误删以前的线上库同样了,而后你能够把表数据从临时库取出来,按须要恢复到线上库去。

当遇到 crash 时,恢复的过程也很是简单:

一、扫描最后一个 Binlog 文件,提取其中的 xid;
二、重作检查点之后的 redo 日志,搜集处于 prepare 阶段的事务链表,将事务的 xid 与 binlog 中的 xid 对比,若存在,则提交,不然就回滚;

总结一下,基本顶多会出现下面是几种状况:

  • 当事务在 prepare 阶段 crash,数据库 recovery 的时候该事务未写入 Binary log 而且存储引擎未提交,将该事务 rollback。
  • 当事务在 binlog 阶段 crash,此时日志尚未成功写入到磁盘中,启动时会 rollback 此事务。
  • 当事务在 binlog 日志已经 fsync 到磁盘后 crash,可是 InnoDB 没有来得及 commit,此时 MySQL 数据库 recovery 的时候将会读出 binlog 中的 xid,而后告诉 InnoDB 提交这些 xid 的事务,InnoDB 提交完这些事务后会回滚其它的事务,使存储引擎和二进制日志始终保持一致。

总结起来讲就是若是一个事务在 prepare 阶段中落盘成功,并在 MySQL Server 层中的 binlog 也写入成功,那这个事务一定 commit 成功。

总结

介绍了 MySQL 里面最重要的两个日志,即物理日志 redo log 和逻辑日志 binlog。

redo log 用于保证 crash-safe 能力。innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数我建议你设置成 1,这样能够保证 MySQL 异常重启以后数据不丢失。

sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样能够保证 MySQL 异常重启以后 binlog 不丢失。

我还跟你介绍了与 MySQL 日志系统密切相关的「两阶段提交」。两阶段提交是跨系统维持数据逻辑一致性时经常使用的一个方案,即便你不作数据库内核开发,平常开发中也有可能会用到。

你的关注是对我最大的鼓励!

最近搜集到传智播客 2018 最新 Python 和 Java 教程!关注本公众号,后台回复「2018」便可获取下载地址。

公众号提供CSDN资源免费下载服务!

相关文章
相关标签/搜索