MySQL实战 | 02-MySQL 如何恢复到半个月内任意一秒的状态?
看到这个题目是不是觉得数据库再也不用担心服务器 crash 了?
那我们需要学习为什么可以这么做?以及如何做?
即为什么可以恢复到任意时间点?如何恢复到任意时间点?
为什么有了 binlog 还需要 redo log?
事务是如何提交的?事务提交先写 binlog 还是 redo log?如何保证这两部分的日志做到顺序一致性?
为了保障主从复制安全,故障恢复是如何做的?
上一次课我们学习了一条 select
语句的全部执行过程,那么今天我们就从一条 update
语句开始。
mysql> update T set c=c+1 where ID=2;
其实执行流程和查询流程一致,只是最后执行器执行的是找到这条数据,并进行更新。
另外,更新过程还涉及到一个重要的日志模块,即 redo log
(重做日志)和 binlog
(归档日志)。
我个人是只听过 binlog 的。
redo log
和大多数关系型数据库一样,InnoDB 记录了对数据文件的物理更改,并保证总是日志先行。
也就是所谓的 WAL(Write-Ahead Logging),即在持久化数据文件前,保证之前的 redo 日志已经写到磁盘。
MySQL 的每一次更新并没有每次都写入磁盘,InnoDB 引擎会先将记录写到 redo log 里,并更新到内存中,然后再适当的时候,再把这个记录更新到磁盘。
这里有必要贴一下 InnoDB 的存储结构图:
如果下面看的各种空间懵逼了,建议回来看一眼这个图。
redo log 是啥
当数据库对数据做修改的时候,需要把数据页从磁盘读到 buffer pool 中,然后在 buffer pool 中进行修改,那么这个时候 buffer pool 中的数据页就与磁盘上的数据页内容不一致,我们称 buffer pool 的数据页为 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/monthly/2015/05/01/
相关配置
默认情况下,对应的物理文件位于数据库的 data 目录下的 ib_logfile1
、ib_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 中。
然后会通过以下三种方式将 innodb 日志缓冲区的日志刷新到磁盘:
1、Master Thread 每秒一次执行刷新 Innodb_log_buffer 到重做日志文件;
2、每个事务提交时会将重做日志刷新到重做日志文件;
3、当 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
配置,也就是说对于非活动的日志文件,在生成时间超过配置的天数之后,会被自动删除。
区别
1、redo log 是 InnoDB 引擎特有的,binlog 是 MySQL 的 Server 层实现,所有引擎都可以使用;
2、内容不同:redo log 是物理日志,记录的是在数据页上做了什么修改,是正在执行中的 dml 以及 ddl 语句;而 binlog 是逻辑日志,记录的是语句的原始逻辑,已经提交完毕之后的 dml 以及 ddl sql 语句,如「给 ID=2 的这一行的 c 字段加 1」;
3、写方式不同:redo log 是循环写的,空间固定;binlog 是可以一直追加写的,一个文件写到一定大小后,会继续写下一个,之前写的文件不会被覆盖;
4、作用不同:redo log 主要用来保证事务安全,作为异常 down 机或者介质故障后的数据恢复使用,binlog 主要用来做主从复制和即时点恢复时使用;
5、另外,两者日志产生的时间,可以释放的时间,在可释放的情况下清理机制,都是完全不同的。
参考:
http://www.importnew.com/28039.html
数据更新事务流程
有了对这两个日志的概念性理解,我们再来看执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程。
1、执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
2、执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
3、引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
4、执行器生成这个操作的 binlog,并把 binlog 写入磁盘;
5、执行器调用引擎的提交事务接口,引擎把刚刚写入的 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
如何恢复数据?
当需要恢复到指定的某一秒时,比如某天下午两点发现中午十二点有一次误删表,需要找回数据,那你可以这么做:
1、首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库;
2、然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻。
这样你的临时库就跟误删之前的线上库一样了,然后你可以把表数据从临时库取出来,按需要恢复到线上库去。
当遇到 crash 时,恢复的过程也非常简单:
1、扫描最后一个 Binlog 文件,提取其中的 xid;
2、重做检查点以后的 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 日志系统密切相关的「两阶段提交」。两阶段提交是跨系统维持数据逻辑一致性时常用的一个方案,即使你不做数据库内核开发,日常开发中也有可能会用到。
MySQL实战 | 02-MySQL 如何恢复到半个月内任意一秒的状态?的更多相关文章
- MYSQL实战-1.mysql基本架构
1.mysql可分为server层和存储引擎 1.1 server层: 连接器.查询缓存.分析器.优化器 .执行器.包含所有内置函数(日期,时间,数学.加密函数),所有跨存储引擎的功能都在此层,比如存 ...
- Angular4+NodeJs+MySQL 入门-02 MySql操作类
NodeJs操作MySQL类 此类封装了几个常用的方法:插入,更新,删除,查询,开启事务,事务提交,事务回滚等操作.有一这个类,操作MYSQL就方便多了. 批处理,存储过程等方法还没有添加,因为觉得目 ...
- MySQL实战45讲学习笔记:日志系统(第二讲)
一.重要的日志模块:redo log 1.通过酒店掌柜记账思路刨析redo log工作原理 2.InnoDB 的 redo log 是固定大小的 只要赊账记录在了粉板上或写了账本上,之后即使掌柜忘记了 ...
- mysql实战45讲读书笔记(二) 一条SQL更新语句是如何执行的 极客时间
前面我们系统了解了一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块.相信你还记得,一条查询语句的执行过程一般是经过连接器.分析器.优化器.执行器等功能模块,最后到达存储引擎. 那么,一条更新语 ...
- 2 (mysql实战) 日志系统
前面我们系统了解了一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块.相信你还记得,一条查询语句的执行过程一般是经过连接器.分析器.优化器.执行器等功能模块,最后到达存储引擎. 那么,一条更新语 ...
- MySQL实战45讲学习笔记:第三十一讲
一.本节概览 今天我要和你讨论的是一个沉重的话题:误删数据. 在前面几篇文章中,我们介绍了 MySQL 的高可用架构.当然,传统的高可用架构是不能预防误删数据的,因为主库的一个 drop table ...
- MySQL高可用方案 MHA之四 keepalived 半同步复制
主从架构(开启5.7的增强半同步模式)master: 10.150.20.90 ed3jrdba90slave: 10.150.20.97 ed3jrdba97 10.150.20.132 ...
- mysql实战之 批量update
mysql实战之批量update 现阶段我们的业务量很小,要对admin_user表中的relationship字段进行更新,指定id是409.已知409是公司内的一服务中心,需要把该服务中心放到区代 ...
- 项目实战6—Mysql实现企业级日志管理、备份与恢复实战
Mysql实现企业级日志管理.备份与恢复实战 环境背景:随着业务的发展,公司业务和规模不断扩大,网站积累了大量的用户信息和数据,对于一家互联网公司来说,用户和业务数据是根基.一旦公司的数据错乱或者丢失 ...
随机推荐
- python之路 正则表达式,模块导入的方法,hashlib加密
一.正则表达式re python中re模块提供了正则表达式相关操作 字符: . 匹配除换行符以外的任意字符 \w 匹配字母或数字或下划线或汉字 \s 匹配任意的空白符 \d 匹配数字 \b 匹配单词的 ...
- Vuex 原理
1.Vuex是什么? 学院派:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式:集中存储和管理应用的所有组件状态. 理解:以上这4个词是我们理解的关键.状态:什么是状态,我们可以通俗的理 ...
- MySQL密码的恢复方法
MySQL密码的恢复方法之一 1.首先确认服务器出于安全的状态,也就是没有人能够任意地连接MySQL数据库. 因为在重新设置MySQL的root密码的期间,MySQL数据库完全出于没有密码保护的 状态 ...
- jq .attr()和.prop()方法的区别
今天在nodejs交流群里面遇到别人在里面说面试的时候遇到了这个问题,没回答出来,面试官讲的他也不明白,这个问题看着很简单,但是往深的解释就很难了. 对于HTML元素本身就带有的固有属性,在处理时,使 ...
- SQL Server 字符串拼接与拆分 string varchar Split and Join
1.Split SQL Server 2008 新语法: DECLARE @str VARCHAR(MAX) SET @str = REPLACE(@teeIDs, ',', '''),(''' ...
- js,java,ajax实现跨域访问及其原理
http://blog.csdn.net/saytime/article/details/51540876 这篇文章对跨域访问做了较为细致得分析,我这里做下简单总结 1.实现跨域访问原理: 浏览器由于 ...
- Spark基本概念快速入门
Spark集群 一组计算机的集合,每个计算机节点作为独立的计算资源,又可以虚拟出多个具备计算能力的虚拟机,这些虚拟机是集群中的计算单元.Spark的核心模块专注于调度和管理虚拟机之上分布式计算任务 ...
- Maven——安装配置
MAVEN 一.介绍:(待填) 二.下载:http://maven.apache.org/download.cgi(官网下载) 选择二进制的zip文件,这种的可直接使用. 三.环境配置 1.前提条件: ...
- 80X86寄存器详解<转载>
引子 打算写几篇稍近底层或者说是基础的博文,浅要介绍或者说是回顾一些基础知识, 自然,还是得从最基础的开始,那就从汇编语言开刀吧, 从汇编语言开刀的话,我们必须还先要了解一些其他东西, 像 CPU ...
- java实现二进制的加法
先看打印结果在看代码比较好理解.结果在最下面的位置. 总结:讲解了二进制的按位异域.按位与.左移的运算规则.并通过次3种算法得到2个数相加的结果.二进制应该还有其他算法,由于知识浅薄就不知道了. 代码 ...