2016-12-23 17:29 宋利兵

作者:宋利兵

来源:MySQL代码研究(mysqlcode)

0、导读

本文重点介绍了InnoDB的crash safe和binlog之间的关系,以及2阶段提交、组提交等概念。看完后,相信您对MySQL Crash Recovery的过程,以及如何保证Crash Safe会有充分的认识。

本文约2200字,阅读时间约15分钟。

0 - 什么是CrashSafe
CrashSafe指MySQL服务器宕机重启后,能够保证:
- 所有已经提交的事务的数据仍然存在。
- 所有没有提交的事务的数据自动回滚。
前面的文章讲过,Innodb通过Redo Log和Undo Log可以保证以上两点。为了保证严格的CrashSafe,必须要在每个事务提交的时候,将Redo Log写入硬件存储。这样做会牺牲一些性能,但是可靠性最好。为了平衡两者,InnoDB提供了一个系统变量,用户可以根据应用的需求自行调整。

- innodb_flush_log_at_trx_commit
  0 - 每N秒将Redo Log Buffer的记录写入Redo Log文件,并且将文件刷入硬件存储1次。N由innodb_flush_log_at_timeout控制。
  1 - 每个事务提交时,将记录从Redo Log Buffer写入Redo Log文件,并且将文件刷入硬件存储。
  2 - 每个事务提交时,仅将记录从Redo Log Buffer写入Redo Log文件。Redo Log何时刷入硬件存储由操作系统和innodb_flush_log_at_timeout决定。这个选项可以保证在MySQL宕机,而操作系统正常工作时,数据的完整性。

那么CrashSafe和Binlog有什么关系呢?

1 - 带Binlog的CrashSafe
当启动Binlog后,事务会产生Binlog Event,这些Event被看做事务数据的一部分。因此要保证事务的Binlog Event和InnoDB引擎中的数据的一致性。所以带Binlog的CrashSafe要求MySQL宕机重启后能够保证:
- 所有已经提交的事务的数据仍然存在。
- 所有没有提交的事务的数据自动回滚。
- 所有已经提交了的事务的Binlog Event也仍然存在。
- 所有没有提交事务没有记录Binlog Event。
  这些要求很好理解,如果重启后数据还在,但是Binlog Event没有了,就没办法复制到其他节点上了。如果重启后,数据没了,但是Binlog Event还在,那么不存在的数据就会被复制到其他节点上,从而导致主从的不一致。

为了保证带Binlog的CrashSafe,MySQL内部使用的两阶段提交(Two Phase Commit)。

2 - MySQL的Two Phase Commit(2PC)
在开启Binlog后,MySQL内部会自动将普通事务当做一个XA事务来处理:

- 自动为每个事务分配一个唯一的ID

- COMMIT会被自动的分成Prepare和Commit两个阶段。

- Binlog会被当做事务协调者(Transaction Coordinator),Binlog Event会被当做协调者日志。

想了解2PC,可以参考文档:https://en.wikipedia.org/wiki/Two-phase_commit_protocol。

- 分布式事务ID(XID)
使用2PC时,MySQL会自动的为每一个事务分配一个ID,叫XID。XID是唯一的,每个事务的XID都不相同。XID会分别被Binlog和InnoDB记入日志中,供恢复时使用。MySQ内部的XID由三部分组成:
- 前缀部分
  前缀部分是字符串"MySQLXid"
- Server ID部分
  当前MySQL的server_id
- query_id部分
  为了保证XID的的唯一性,数字部分使用了query_id。MySQL内部会自动的为每一个语句分配一个query_id,全局唯一。
参考代码:sql/xa。h的 struct xid_t结构。

- 事务的协调者Binlog
Binlog在2PC中充当了事务的协调者(Transaction Coordinator)。由Binlog来通知InnoDB引擎来执行prepare,commit或者rollback的步骤。事务提交的整个过程如下:
1. 协调者准备阶段(Prepare Phase)
    告诉引擎做Prepare,InnoDB更改事务状态,并将Redo Log刷入磁盘。
2. 协调者提交阶段(Commit Phase)
    2.1 记录协调者日志,即Binlog日志。
    2.2  告诉引擎做commit。

注意:记录Binlog是在InnoDB引擎Prepare(即Redo Log写入磁盘)之后,这点至关重要。

在MySQ的代码中将协调者叫做tc_log。在MySQL启动时,tc_log将被初始化为mysql_bin_log对象。参考sql/mysqld.cc中的init_server_components():
if (opt_bin_log)
   tc_log= &mysql_bin_log;
而在事务提交时,会依次执行:
tc_log->prepare();
tc_log->commit();
参考代码:sql/handler.cc中的ha_commit_trans()。

当mysql_bin_log是tc_log时,prepare和commit的代码在sql/binlog.cc中:
MYSQL_BIN_LOG::prepare()
MYSQL_BIN_LOG::commit()

- 协调者日志Xid_log_event
作为协调者,Binlog需要将事务的XID记入日志,供恢复时使用。Xid_log_event有以下几个特点:
- 仅记录query_id
  因为前缀部分不变,server_id已经记录在Event Header中,Xid_log_event中只记录query_id部分。
- 标志事务的结束  
  在Binlog中相当于一个事务的COMMIT语句。
  一个事务在Binlog中看起来时这样的:

Query_log_event("BEGIN");
DML产生的events;              
Xid_log_event;

- DDL没有BEGIN,也没有Xid_log_event。

- 仅InnoDB的DML会产生Xid_log_event
  因为MyISAM不支持2PC所以不能用Xid_log_event,但会有COMMIT Event。

Query_log_event("BEGIN");
DML产生的events;
Query_log_event("COMMIT");

问题:Query_log_event("COMMIT")和Xid_log_event有不同的影响吗?
- Xid_log_event中的Xid可以帮助master实现CrashSafe。
- Slave的CrashSafe不依赖Xid_log_event

事务在Slave上重做时,会重新产生XID。所以Slave服务器的CrashSafe并不依赖于Xid_log_event。Xid_log_event和Query_log_event("COMMIT"),只是作为事务的结尾,告诉Slave Applier去提交这个事务。因此二者在Slave上的影响是一样的。

3 - 恢复(Recovery)
这个机制是如何保证MySQL的CrashSafe的呢,我们来分析一下。这里我们假设用户设置了以下参数来保证可靠性:
sync_binlog=1
innodb_flush_log_at_trx_commit=1

- 恢复前事务的状态
在恢复开始前事务有以下几种状态:
- InnoDB中已经提交
  根据前面2PC的过程,可知Binlog中也一定记录了该事务的的Events。所以这种事务是一致的不需要处理。
- InnoDB中是prepared状态,Binlog中有该事务的Events。
  需要通知InnoDB提交这些事务。
- InnoDB中是prepared状态,Binlog中没有该事务的Events。
  因为Binlog还没记录,需要通知InnoDB回滚这些事务。
- Before InnoDB Prepare
  事务可能还没执行完,因此InnoDB中的状态还没有prepare。根据2PC的过程,Binlog中也没有该事务的events。 需要通知InnoDB回滚这些事务。

- 恢复过程
从上面的事务状态可以看出:恢复时事务要提交还是回滚,是由Binlog来决定的。
- 事务的Xid_log_event存在,就要提交。
- 事务的Xid_log_event不存在,就要回滚。

恢复的过程非常简单:
- 从Binlog中读出所有的Xid_log_event
- 告诉InnoDB提交这些XID的事务
- InnoDB回滚其它的事务

疑问1:如果事务的Binlog Event只记录了一部分怎么办?
只有最后一个事务的Event会发生这样的情况。在恢复时,binlog会自动的将这个不完整的事务Events从Binlog文件中给清除掉。

疑问2:随着长时间的运行,Binlog中会积累了很多Xid_log_event,读取所有的Xid_log_event会不会效率很低?

当然很低,所以Binlog中有一个机制来保证恢复时只用读取最后一个Binlog文件中的Xid_log_event。这种机制很像一个简单的Xid_log_event的checkpoint机制。

- Xid_log_event Checkpoint

这个机制和binlog的文件切换有关,在切换到一个新的Binlog文件前:
- 要等待当前Binlog文件中的所有事务都已经在InnoDB中提交了。
- 告诉InnoDB刷Redo Log到硬件存储。
通过这个机制可以保证在做恢复时,除了最后一个Binlog文件中的事务,其他文件中的事务在InnoDB中一定是已经提交的状态。

参考代码:sql/binlog.cc中:

MYSQL_BIN_LOG::recovery()

MYSQL_BIN_LOG::new_file_impl()

MYSQL_BIN_LOG::inc_prep_xids()

MYSQL_BIN_LOG::dec_prep_xids()

4 - CrashSafe的写盘次数
前面说道要想保证CrashSafe就要设置下面两个参数为1:
sync_binlog=1
innodb_flush_log_at_trx_commit=1
下面我们来看看这两个参数的作用。
- sync_binlog
sync_binlog是控制Binlog写盘的,1表示每次都写。由于Binlog使用了组提交(Group Commit)的机制,它代表一组事务提交时必须要将Binlog文件写入硬件存储1次。
- innodb_flush_log_at_trx_commit的写盘次数
这个变量是用来控制InnoDB commit时写盘的方法的。现在commit被分成了两个阶段,到底在哪个阶段写盘,还是两个阶段都要写盘呢?
- Prepare阶段时需要写盘
 2PC要求在Prepare时就要将数据持久化,只有这样,恢复时才能提交已经记录了Xid_log_event的事务。
- Commit阶段时不需要写盘
 如果Commit阶段不写盘,会造成什么结果呢?已经Cmmit了的事务,在恢复时的状态可能是Prepared。由于恢复时,Prepared的事务可以通过Xid_log_event来提交事务,所以在恢复后事务的状态就是正确的。因此在Commit阶段不需要写盘。

总的来说保证MySQL服务的CrashSafe需要写两次盘。在2PC的过程中,InnoDB只在prepare阶段时,写一次盘。Binlog在commit阶段,会设置一个参数告诉InnoDB不要写盘。这个参数是thd->durability_property= HA_IGNORE_DURABILITY;代码在sql/binlog.cc的MYSQL_BIN_LOG::ordered_commit()中。

- Prepare阶段写盘优化

我们知道Binlog使用了Group Commit机制来减少IO,提高性能。Prepare有没有可能做Group Commit呢?只要我们能保证任何事务的Redo Log是在它的Binlog Event写入Binlog文件前,被刷入了持久存储就可以。优化后的做法是:
1. 协调者准备阶段(Prepare Phase)
   设置thd->durability_property告诉InnoDB不写盘。 告诉引擎做Prepare,InnoDB更改事务状态。
2. 协调者提交阶段(Commit Phase)
   2.1.1 获取一组事务。
   2.1.2 通知InnoDB将Redo Log写入硬件存储。
   2.1.3 将这组事务的Binlog Event写入Binlog文件。
   2.2 告诉引擎做commit。

这个结合了Binlog Group Commit机制的改进对性能的提升还是很显著的。而且这个改进是中国的社区用户阿里云的翟卫祥同学提出并提供的代码补丁。详情可参考MySQL的bug页面:http://bugs.mysql.com/bug.php?id=73202。

参考代码:sql/binlog.cc中的MYSQL_BIN_LOG::process_flush_stage_queue()

5 - 总结

MySQL通过两阶段提交的方式来保证CrashSafe。CrashSafe需要Server层、Binlog和InnoDB的协同工作才能完成。由于DDL和MyISAM不支持事务性,因此没办法保证CrashSafe。

注意:本文的代码都是指MySQL-5.7中的代码,其他版本可能会有不一致。

延伸阅读,宋利兵老师其他文章

MySQL数据库InnoDB存储引擎Log漫游(1)

MySQL数据库InnoDB存储引擎Log漫游(2)

MySQL数据库InnoDB存储引擎Log漫游(3)

由浅入深理解InnoDB的索引实现(1)

由浅入深理解InnoDB的索引实现(2)

MySQL crash-safe replication(3): MySQL的Crash Safe和Binlog的关系的更多相关文章

  1. MySQL 5.7 Replication 相关新功能说明

    背景: MySQL5.7在主从复制上面相对之前版本多了一些新特性,包括多源复制.基于组提交的并行复制.在线修改Replication Filter.GTID增强.半同步复制增强等.因为都是和复制相关, ...

  2. MySQL5.7 Group Replication (MGR)--Mysql的组复制之多主模式

    MGR——Mysql的组复制之多主模式 以下测试在VMware环境: 操作系统:Centos 6.9 X86_64 数据库:Mysql 5.7 (mysql  Ver 14.14 Distrib 5. ...

  3. MySQL 5.7 Replication 相关新功能说明 (转)

    背景: MySQL5.7在主从复制上面相对之前版本多了一些新特性,包括多源复制.基于组提交的并行复制.在线修改Replication Filter.GTID增强.半同步复制增强等.因为都是和复制相关, ...

  4. MySql(十三):MySql架构设计——可扩展性设计之 MySQL Replication

    一.前言 MySQL Replication能够将一个 MySQL Server 的 Instance 中的数据完整的复制到另外一个 MySQL Server 的 Instance 中.虽然复制过程并 ...

  5. System.TypeInitializationException: 'The type initializer for 'MySql.Data.MySqlClient.Replication.ReplicationManager' threw an exception.'

    下午在调试的时候报错数据库连接就报错我就很纳闷后面用原来的代码写发现还是报错 System.TypeInitializationException: 'The type initializer for ...

  6. 《MySQL 5.7 Replication新特性》分享之互动问题解答

    原创 2016-07-21 宋利兵 MySQL中文网 分享主题 <MySQL 5.7 Replication新特性> 嘉宾介绍 宋利兵,MySQL研发工程师.2009年加入MySQL全球研 ...

  7. MySql NDB cluster replication配置

    文章目录 单机部署: 1. 创建ndb_mgmd配置文件: 2. 创建ndbd配置文件 3. 创建mysqld配置文件 4. 安装初始数据库 5. 按顺序启动ndb_mgmd, ndbd, mysql ...

  8. MySql(十七):MySql架构设计——高可用设计之思路及方案

    前言: 数据库系统是一个应用系统的核心部分,要想系统整体可用性得到保证,数据库系统就不能出现任何问题.对于一个企业级的系统来说,数据库系统的可用性尤为重要.数据库系统一旦出现问题无法提供服务,所有系统 ...

  9. MySql(十):MySQL性能调优——MySQL Server性能优化

    本章主要通过针对MySQL Server( mysqld)相关实现机制的分析,得到一些相应的优化建议.主要涉及MySQL的安装以及相关参数设置的优化,但不包括mysqld之外的比如存储引擎相关的参数优 ...

  10. MySql(十一):MySQL性能调优——常用存储引擎优化

    一.前言 MySQL 提供的非常丰富的存储引擎种类供大家选择,有多种选择固然是好事,但是需要我们理解掌握的知识也会增加很多.本章将介绍最为常用的两种存储引擎进行针对性的优化建议. 二.MyISAM存储 ...

随机推荐

  1. WPF中的Visual Tree和Logical Tree与路由事件

    1.Visual Tree和Logical TreeLogical Tree:逻辑树,WPF中用户界面有一个对象树构建而成,这棵树叫做逻辑树,元素的声明分层结构形成了所谓的逻辑树!!Visual Tr ...

  2. -bash: warning: setlocale: LC_CTYPE: cannot change locale (zh_US.UTF-8): No such file or directory -bash: warning: setlocale: LC_COLLATE:

    前几天登录服务器发现出现了这些个警告,一直没时间去处理他,今天难得有空,处理一下并记录下来,希望可以帮助到有需要的朋友. 警告信息如下: Last :: from 10.0.0.1 -bash: wa ...

  3. offsetTop、offsetLeft、offsetWidth、offsetHeight的用法

    假设 obj 为某个 HTML 控件. obj.offsetTop 指 obj 相对于版面或由 offsetParent 属性指定的父坐标的计算上侧位置,整型,单位像素. obj.offsetLeft ...

  4. Druid链接池配置加密密码链接数据库

    Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0.DBCP.PROXOOL等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB ...

  5. Linux中locate命令的实践总结

    locate 让使用者可以很快速的搜寻档案系统内是否有指定的档案.其方法是先建立一个包括系统内所有档案名称及路径的数据库,之后当寻找时就只需查询这个数据库,而不必实际深入档案系统之中了.在一般的 di ...

  6. golang中的接口实现(一)

    golang中的接口实现 // 定义一个接口 type People interface { getAge() int // 定义抽象方法1 getName() string // 定义抽象方法2 } ...

  7. 使用spring security 2.0 和extjs 3.0实现web登录

    使用spring security 2.0 和extjs 3.0实现web登录 1开发环境说明 本例使用MyEclipse 6.5作为开发工具,jdk1.5作为编译工具,tomcat6.0作为web运 ...

  8. mahout 使用

    最近在做mahout源码调用的时候,发现一个参数:startPhase和endPhase,这两个参数是什么意思呢?比如运行RecommenderJob时,可以看到10个MR任务,所以猜测是否是一个ph ...

  9. Java基础——ArrayList与LinkedList(二)

    今天练习ArrayList与LinkedList,在网上看到有关它俩应用效率的题型.觉得很有价值,保留一下. import java.util.ArrayList; import java.util. ...

  10. mybatis将传入0识别成空字符串

    mybatis将传入的Integer类型的0被识别成空字符串,网上的解决办法: <if test="status != null and status != '' or status ...