learn.lianglianglee.com/文章/MySQL 主从复制.md.html
2022-08-14 03:40:33 +08:00

1413 lines
96 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>MySQL 主从复制.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/文章/AQS 万字图文全面解析.md.html">AQS 万字图文全面解析</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md.html">Docker 镜像构建原理及源码分析</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md.html">ElasticSearch 小白从入门到精通</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md.html">JVM CPU Profiler技术原理及源码深度解析</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md.html">JVM 垃圾收集器</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md.html">JVM 面试的 30 个知识点</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md.html">Java IO 体系、线程模型大总结</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md.html">Java NIO浅析</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md.html">Java 面试题集锦(网络篇)</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md.html">Java-直接内存 DirectMemory 详解</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决</a>
</li>
<li>
<a href="/文章/Java中的SPI.md.html">Java中的SPI</a>
</li>
<li>
<a href="/文章/Java中的ThreadLocal.md.html">Java中的ThreadLocal</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">Java线程池实现原理及其在美团业务中的实践</a>
</li>
<li>
<a href="/文章/Java魔法类Unsafe应用解析.md.html">Java魔法类Unsafe应用解析</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md.html">Kafka 源码阅读笔记</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md.html">MySQL · 引擎特性 · InnoDB Buffer Pool</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md.html">MySQL · 引擎特性 · InnoDB IO子系统</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md.html">MySQL · 引擎特性 · InnoDB 事务系统</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md.html">MySQL · 引擎特性 · InnoDB 同步机制</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md.html">MySQL · 引擎特性 · InnoDB 数据页解析</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">MySQL · 引擎特性 · InnoDB崩溃恢复</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html">MySQL · 引擎特性 · 临时表那些事儿</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md.html">MySQL 主从复制 半同步复制</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">MySQL 主从复制 基于GTID复制</a>
</li>
<li>
<a class="current-tab" href="/文章/MySQL 主从复制.md.html">MySQL 主从复制</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">MySQL 事务日志(redo log和undo log)</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md.html">MySQL 亿级别数据迁移实战代码分享</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md.html">MySQL 从一条数据说起-InnoDB行存储数据结构</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md.html">MySQL 地基基础:事务和锁的面纱</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md.html">MySQL 地基基础:数据字典</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md.html">MySQL 地基基础:数据库字符集</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md.html">MySQL 性能优化:碎片整理</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md.html">MySQL 故障诊断:如何在日志中轻松定位大事务</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md.html">MySQL 故障诊断:教你快速定位加锁的 SQL</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md.html">MySQL 日志详解</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md.html">MySQL 的半同步是什么?</a>
</li>
<li>
<a href="/文章/MySQL中的事务和MVCC.md.html">MySQL中的事务和MVCC</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">MySQL事务_事务隔离级别详解</a>
</li>
<li>
<a href="/文章/MySQL优化优化 select count().md.html">MySQL优化优化 select count()</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md.html">MySQL共享锁、排他锁、悲观锁、乐观锁</a>
</li>
<li>
<a href="/文章/MySQL的MVCC多版本并发控制.md.html">MySQL的MVCC多版本并发控制</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md.html">QingStor 对象存储架构设计及最佳实践</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md.html">RocketMQ 面试题集锦</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md.html">SnowFlake 雪花算法生成分布式 ID</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html">Spring Boot 2.x 结合 k8s 实现分布式微服务架构</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">Spring Boot 教程:如何开发一个 starter</a>
</li>
<li>
<a href="/文章/Spring MVC 原理.md.html">Spring MVC 原理</a>
</li>
<li>
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">Spring 帮助你更好的理解Spring循环依赖</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md.html">Spring 循环依赖及解决方式</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md.html">Spring中眼花缭乱的BeanDefinition</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md.html">Vert.x 基础入门</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md.html">eBay 的 Elasticsearch 性能调优实践</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md.html">不可不说的Java“锁”事</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md.html">互联网并发限流实战</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md.html">从ReentrantLock的实现看AQS的原理及应用</a>
</li>
<li>
<a href="/文章/从SpringCloud开始聊微服务架构.md.html">从SpringCloud开始聊微服务架构</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md.html">全面了解 JDK 线程池实现原理</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md.html">分布式一致性理论与算法</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md.html">分布式一致性算法 Raft</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md.html">分布式唯一 ID 解析</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md.html">分布式链路追踪:集群管理设计</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md.html">动态代理种类及原理,你知道多少?</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md.html">响应式架构与 RxJava 在有赞零售的实践</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md.html">大数据算法——布隆过滤器</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md.html">如何优雅地记录操作日志?</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">如何设计一个亿级消息量的 IM 系统</a>
</li>
<li>
<a href="/文章/异步网络模型.md.html">异步网络模型</a>
</li>
<li>
<a href="/文章/当我们在讨论CQRS时我们在讨论些神马.md.html">当我们在讨论CQRS时我们在讨论些神马</a>
</li>
<li>
<a href="/文章/彻底理解 MySQL 的索引机制.md.html">彻底理解 MySQL 的索引机制</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">最全的 116 道 Redis 面试题解答</a>
</li>
<li>
<a href="/文章/有赞权限系统(SAM).md.html">有赞权限系统(SAM)</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">有赞零售中台建设方法的探索与实践</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html">服务注册与发现原理剖析Eureka、Zookeeper、Nacos</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md.html">深入浅出Cache</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md.html">深入理解 MySQL 底层实现</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md.html">漫画讲解 git rebase VS git merge</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md.html">生成浏览器唯一稳定 ID 的探索</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md.html">缓存 如何保证缓存与数据库的双写一致性?</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md.html">网易严选怎么做全链路监控的?</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md.html">美团万亿级 KV 存储架构与实践</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md.html">美团点评Kubernetes集群管理实践</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md.html">美团百亿规模API网关服务Shepherd的设计与实现</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md.html">解读《阿里巴巴 Java 开发手册》背后的思考</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md.html">认识 MySQL 和 Redis 的数据一致性问题</a>
</li>
<li>
<a href="/文章/进阶Dockerfile 高阶使用指南及镜像优化.md.html">进阶Dockerfile 高阶使用指南及镜像优化</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md.html">铁总在用的高性能分布式缓存计算框架 Geode</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md.html">面试最常被问的 Java 后端题</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md.html">领域驱动设计在互联网业务开发中的实践</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md.html">领域驱动设计的菱形对称架构</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md.html">高效构建 Docker 镜像的最佳实践</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>MySQL 主从复制</h1>
<p>本文非常详细地介绍MySQL复制相关的内容包括基本概念、复制原理、如何配置不同类型的复制(传统复制)等等。在此文章之后还有几篇文章分别介绍GTID复制、半同步复制、实现MySQL的动静分离以及MySQL 5.7.17引入的革命性功能:组复制(MGR)。</p>
<p>本文是MySQL Replication的基础但却非常重要。对于MySQL复制如何搭建它不是重点(因为简单,网上资源非常多),如何维护它才是重点(网上资源不集中)。以下几个知识点是掌握MySQL复制所必备的</p>
<ol>
<li>复制的原理</li>
<li>将master上已存在的数据恢复到slave上作为基准数据</li>
<li>获取<strong>正确的</strong>binlog坐标</li>
<li><strong>深入理解</strong><code>show slave status</code>中的一些状态信息</li>
</ol>
<p>本文对以上内容都做了非常详细的说明。希望对各位初学、深入MySQL复制有所帮助。</p>
<p>mysql replication官方手册<a href="https://dev.mysql.com/doc/refman/5.7/en/replication.html。">https://dev.mysql.com/doc/refman/5.7/en/replication.html。</a></p>
<h1>1.复制的基本概念和原理</h1>
<p>mysql复制是指从一个mysql服务器(MASTER)将数据<strong>通过日志的方式经过网络传送</strong>到另一台或多台mysql服务器(SLAVE)然后在slave上重放(replay或redo)传送过来的日志以达到和master数据同步的目的。</p>
<p>它的工作原理很简单。首先<strong>确保master数据库上开启了二进制日志这是复制的前提</strong></p>
<ul>
<li>
<p>在slave准备开始复制时首先<strong>要执行change master to语句设置连接到master服务器的连接参数</strong>在执行该语句的时候要提供一些信息包括如何连接和要从哪复制binlog这些信息在连接的时候会记录到slave的datadir下的master.info文件中以后再连接master的时候将不用再提供这新信息而是直接读取该文件进行连接。</p>
</li>
<li>
<p>在slave上有两种线程</p>
<p>分别是IO线程和SQL线程</p>
<ul>
<li>IO线程用于连接master监控和接受master的binlog。当启动IO线程成功连接master时<strong>master会同时启动一个dump线程</strong>该线程将slave请求要复制的binlog给dump出来之后IO线程负责监控并接收master上dump出来的二进制日志当master上binlog有变化的时候IO线程就将其复制过来并写入到自己的中继日志(relay log)文件中。</li>
<li>slave上的另一个线程SQL线程用于监控、读取并重放relay log中的日志将数据写入到自己的数据库中。如下图所示。</li>
</ul>
</li>
</ul>
<p>站在slave的角度上看过程如下</p>
<p><img src="assets/733013-20180524163346938-787080496.png" alt="img" /></p>
<p>站在master的角度上看过程如下(默认的异步复制模式,前提是设置了<code>sync_binlog=1</code>否则binlog刷盘时间由操作系统决定)</p>
<p><img src="assets/733013-20180610221451539-1794738810.png" alt="img" /></p>
<p>所以,可以认为复制大致有三个步骤:</p>
<ol>
<li>数据修改写入master数据库的binlog中。</li>
<li>slave的IO线程复制这些变动的binlog到自己的relay log中。</li>
<li>slave的SQL线程读取并重新应用relay log到自己的数据库上让其和master数据库保持一致。</li>
</ol>
<p>从复制的机制上可以知道在复制进行前slave上必须具有master上部分完整内容作为复制基准数据。例如master上有数据库A二进制日志已经写到了pos1位置那么在复制进行前slave上必须要有数据库A且如果要从pos1位置开始复制的话还必须有和master上pos1之前完全一致的数据。如果不满足这样的一致性条件那么在replay中继日志的时候将不知道如何进行应用而导致数据混乱。<strong>也就是说复制是基于binlog的position进行的复制之前必须保证position一致。</strong>(注:这是传统的复制方式所要求的)</p>
<p>可以选择对哪些数据库甚至数据库中的哪些表进行复制。<strong>默认情况下MySQL的复制是异步的。slave可以不用一直连着master即使中间断开了也能从断开的position处继续进行复制。</strong></p>
<p>MySQL 5.6对比MySQL 5.5在复制上进行了很大的改进主要包括支持GTID(Global Transaction ID,全局事务ID)复制和多SQL线程并行重放。GTID的复制方式和传统的复制方式不一样通过全局事务ID它不要求复制前slave有基准数据也不要求binlog的position一致。</p>
<p>MySQL 5.7.17则提出了组复制(MySQL Group Replication,MGR)的概念。像数据库这样的产品必须要尽可能完美地设计一致性问题特别是在集群、分布式环境下。Galera就是一个MySQL集群产品它支持多主模型(多个master)但是当MySQL 5.7.17引入了MGR功能后Galera的优势不再明显甚至MGR可以取而代之。MGR为MySQL集群中多主复制的很多问题提供了很好的方案可谓是一项革命性的功能。</p>
<p>复制和二进制日志息息相关,所以学习本章必须先有二进制日志的相关知识。</p>
<h1>2.复制的好处</h1>
<p>围绕下面的拓扑图来分析:</p>
<p><img src="assets/733013-20180524173723814-389803553.png" alt="img" /></p>
<p>主要有以下几点好处:</p>
<p><strong>1.提供了读写分离的能力。</strong></p>
<p>replication让所有的slave都和master保持数据一致因此外界客户端可以从各个slave中读取数据而写数据则从master上操作。也就是实现了读写分离。</p>
<p>需要注意的是,为了保证数据一致性,<strong>写操作必须在master上进行</strong></p>
<p>通常说到读写分离这个词,立刻就能意识到它会分散压力、提高性能。</p>
<p><strong>2.为MySQL服务器提供了良好的伸缩(scale-out)能力。</strong></p>
<p>由于各个slave服务器上只提供数据检索而没有写操作因此&quot;随意地&quot;增加slave服务器数量来提升整个MySQL群的性能而不会对当前业务产生任何影响。</p>
<p>之所以&quot;随意地&quot;要加上双引号是因为每个slave都要和master建立连接传输数据。如果slave数量巨多master的压力就会增大网络带宽的压力也会增大。</p>
<p><strong>3.数据库备份时,对业务影响降到最低。</strong></p>
<p>由于MySQL服务器群中所有数据都是一致的(至少几乎是一致的)所以在需要备份数据库的时候可以任意停止某一台slave的复制功能(甚至停止整个mysql服务),然后从这台主机上进行备份,这样几乎不会影响整个业务(除非只有一台slave但既然只有一台slave说明业务压力并不大短期内将这个压力分配给master也不会有什么影响)。</p>
<p><strong>4.能提升数据的安全性。</strong></p>
<p>这是显然的任意一台mysql服务器断开都不会丢失数据。即使是master宕机也只是丢失了那部分还没有传送的数据(异步复制时才会丢失这部分数据)。</p>
<p><strong>5.数据分析不再影响业务。</strong></p>
<p>需要进行数据分析的时候直接划分一台或多台slave出来专门用于数据分析。这样OLTP和OLAP可以共存且几乎不会影响业务处理性能。</p>
<h1>3.复制分类和它们的特性</h1>
<p>MySQL支持两种不同的复制方法传统的复制方式和GTID复制。MySQL 5.7.17之后还支持组复制(MGR)。</p>
<ul>
<li>(1).传统的复制方法要求复制之前slave上必须有基准数据且binlog的position一致。</li>
<li>(2).GTID复制方法不要求基准数据和binlog的position一致性。GTID复制时master上只要一提交就会立即应用到slave上。这极大地简化了复制的复杂性且更好地保证master上和各slave上的数据一致性。</li>
</ul>
<p>从数据同步方式的角度考虑MySQL支持4种不同的同步方式同步(synchronous)、半同步(semisynchronous)、异步(asynchronous)、延迟(delayed)。所以对于复制来说,就分为同步复制、半同步复制、异步复制和延迟复制。</p>
<h2>3.1 同步复制</h2>
<p>客户端发送DDL/DML语句给mastermaster执行完毕后还需要<strong>等待所有的slave都写完了relay log才认为此次DDL/DML成功然后才会返回成功信息给客户端</strong>。同步复制的问题是master必须等待所以延迟较大在MySQL中不使用这种复制方式。</p>
<p><img src="assets/733013-20180524204948187-2045181991.png" alt="img" /></p>
<p>例如上图中描述的只有3个slave全都写完relay log并返回ACK给master后master才会判断此次DDL/DML成功。</p>
<h2>3.2 半同步复制</h2>
<p>客户端发送DDL/DML语句给mastermaster执行完毕后<strong>还要等待一个slave写完relay log并返回确认信息给mastermaster才认为此次DDL/DML语句是成功的然后才会发送成功信息给客户端</strong>。半同步复制只需等待一个slave的回应且等待的超时时间可以设置超时后会自动降级为异步复制所以在局域网内(网络延迟很小)使用半同步复制是可行的。</p>
<p><img src="assets/733013-20180524205148967-868029789.png" alt="img" /></p>
<p>例如上图中只有第一个slave返回成功master就判断此次DDL/DML成功其他的slave无论复制进行到哪一个阶段都无关紧要。</p>
<h2>3.3 异步复制</h2>
<p>客户端发送DDL/DML语句给master<strong>master执行完毕立即返回成功信息给客户端而不管slave是否已经开始复制</strong>。这样的复制方式导致的问题是当master写完了binlog而slave还没有开始复制或者复制还没完成时<strong>slave上和master上的数据暂时不一致且此时master突然宕机slave将会丢失一部分数据。如果此时把slave提升为新的master那么整个数据库就永久丢失这部分数据。</strong></p>
<p><img src="assets/733013-20180524205215240-203795747.png" alt="img" /></p>
<h2>3.4 延迟复制</h2>
<p>顾名思义延迟复制就是故意让slave延迟一段时间再从master上进行复制。</p>
<h1>4.配置一主一从</h1>
<p>此处先配置默认的异步复制模式。由于复制和binlog息息相关如果对binlog还不熟悉请先了解binlog<a href="https://www.cnblogs.com/f-ck-need-u/p/9001061.html#blog5">详细分析二进制日志</a></p>
<p>mysql支持一主一从和一主多从。但是每个slave必须只能是一个master的从否则从多个master接受二进制日志后重放将会导致数据混乱的问题。</p>
<p>以下是一主一从的结构图:
<img src="assets/733013-20180528163847611-1424365065.png" alt="img" /></p>
<p>在开始传统的复制(非GTID复制)前,需要完成以下几个关键点,<strong>这几个关键点指导后续复制的所有步骤</strong></p>
<ol>
<li>为master和slave设定不同的<code>server-id</code>这是主从复制结构中非常关键的标识号。到了MySQL 5.7似乎不设置server id就无法开启binlog。设置server id需要重启MySQL实例。</li>
<li>开启master的binlog。刚安装并初始化的MySQL默认未开启binlog建议手动设置binlog且为其设定文件名否则默认以主机名为基名时修改主机名后会找不到日志文件。</li>
<li>最好设置master上的变量<code>sync_binlog=1</code>(MySQL 5.7.7之后默认为1之前的版本默认为0)这样每写一次二进制日志都将其刷新到磁盘让slave服务器可以尽快地复制。防止万一master的二进制日志还在缓存中就宕机时slave无法复制这部分丢失的数据。</li>
<li>最好设置master上的redo log的刷盘变量<code>innodb_flush_log_at_trx_commit=1</code>(默认值为1),这样每次提交事务都会立即将事务刷盘保证持久性和一致性。</li>
<li>在slave上开启中继日志relay log。这个是默认开启的同样建议手动设置其文件名。</li>
<li>建议在master上专门创建一个用于复制的用户它只需要有复制权限<code>replication slave</code>用来读取binlog。</li>
<li>确保slave上的数据和master上的数据在&quot;复制的起始position之前&quot;是完全一致的。如果master和slave上数据不一致复制会失败。</li>
<li>记下master开始复制前binlog的position因为在slave连接master时需要指定从master的哪个position开始复制。</li>
<li>考虑是否将slave设置为只读也就是开启<code>read_only</code>选项。这种情况下除了具有super权限(mysql 5.7.16还提供了<code>super_read_only</code>禁止super的写操作)和SQL线程能写数据库其他用户都不能进行写操作。这种禁写对于slave来说绝大多数场景都非常适合。</li>
</ol>
<h2>4.1 一主一从</h2>
<p>一主一从是最简单的主从复制结构。本节实验环境如下:</p>
<p><img src="assets/733013-20180528194339716-360937433.png" alt="img" /></p>
<ol>
<li><strong>配置master和slave的配置文件。</strong></li>
</ol>
<pre><code>
[mysqld] # master
datadir=/data
socket=/data/mysql.sock
log-bin=master-bin
sync-binlog=1
server-id=100
[mysqld] # slave
datadir=/data
socket=/data/mysql.sock
relay-log=slave-bin
server-id=111
</code></pre>
<ol>
<li>重启master和slave上的MySQL实例。</li>
</ol>
<pre><code>service mysqld restart
</code></pre>
<ol>
<li><strong>在master上创建复制专用的用户。</strong></li>
</ol>
<pre><code>create user 'repl'@'192.168.100.%' identified by '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="80d0c0f3f3f7eff2e4b1">[email&#160;protected]</a>!';
grant REPLICATION SLAVE on *.* to 'repl'@'192.168.100.%';
</code></pre>
<ol>
<li><strong>将slave恢复到master上指定的坐标。</strong>
这是备份恢复的内容,此处用一个小节来简述操作过程。详细内容见<a href="https://www.cnblogs.com/f-ck-need-u/p/9013458.html">MySQL备份和恢复(一)、(二)、(三)</a></li>
</ol>
<h2>4.2 将slave恢复到master指定的坐标</h2>
<p>对于复制而言,有几种情况:</p>
<ul>
<li>(1).待复制的master没有新增数据例如新安装的mysql实例。这种情况下可以跳过恢复这个过程。</li>
<li>(2).待复制的master上已有数据。这时需要将这些已有数据也应用到slave上并获取master上binlog当前的坐标。只有slave和master的数据能匹配上slave重放relay log时才不会出错。</li>
</ul>
<p>第一种情况此处不赘述。第二种情况有几种方法例如使用mysqldump、冷备份、xtrabackup等工具这其中又需要考虑是MyISAM表还是InnoDB表。</p>
<p>在实验开始之前首先在master上新增一些测试数据以innodb和myisam的数值辅助表为例。</p>
<pre><code>DROP DATABASE IF EXISTS backuptest;
CREATE DATABASE backuptest;
USE backuptest;
# 创建myisam类型的数值辅助表和插入数据的存储过程
CREATE TABLE num_isam (n INT NOT NULL PRIMARY KEY) ENGINE = MYISAM ;
DROP PROCEDURE IF EXISTS proc_num1;
DELIMITER $$
CREATE PROCEDURE proc_num1 (num INT)
BEGIN
DECLARE rn INT DEFAULT 1 ;
TRUNCATE TABLE backuptest.num_isam ;
INSERT INTO backuptest.num_isam VALUES(1) ;
dd: WHILE rn * 2 &lt; num DO
BEGIN
INSERT INTO backuptest.num_isam
SELECT rn + n FROM backuptest.num_isam;
SET rn = rn * 2 ;
END ;
END WHILE dd;
INSERT INTO backuptest.num_isam
SELECT n + rn
FROM backuptest.num_isam
WHERE n + rn &lt;= num;
END ;
$$
DELIMITER ;
# 创建innodb类型的数值辅助表和插入数据的存储过程
CREATE TABLE num_innodb (n INT NOT NULL PRIMARY KEY) ENGINE = INNODB ;
DROP PROCEDURE IF EXISTS proc_num2;
DELIMITER $$
CREATE PROCEDURE proc_num2 (num INT)
BEGIN
DECLARE rn INT DEFAULT 1 ;
TRUNCATE TABLE backuptest.num_innodb ;
INSERT INTO backuptest.num_innodb VALUES(1) ;
dd: WHILE rn * 2 &lt; num DO
BEGIN
INSERT INTO backuptest.num_innodb
SELECT rn + n FROM backuptest.num_innodb;
SET rn = rn * 2 ;
END ;
END WHILE dd;
INSERT INTO backuptest.num_innodb
SELECT n + rn
FROM backuptest.num_innodb
WHERE n + rn &lt;= num ;
END ;
$$
DELIMITER ;
# 分别向两个数值辅助表中插入100W条数据
CALL proc_num1 (1000000) ;
CALL proc_num2 (1000000) ;
</code></pre>
<p>所谓数值辅助表是只有一列的表且这个字段的值全是数值从1开始增长。例如上面的是从1到100W的数值辅助表。</p>
<pre><code>mysql&gt; select * from backuptest.num_isam limit 10;
+----+
| n |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+----+
</code></pre>
<h3>4.2.1 获取master binlog的坐标</h3>
<p><strong>如果master是全新的数据库实例或者在此之前没有开启过binlog那么它的坐标位置是position=4</strong>。之所以是4而非0是因为binlog的前4个记录单元是每个binlog文件的头部信息。</p>
<p>如果master已有数据或者说master以前就开启了binlog并写过数据库那么需要手动获取position。<strong>为了安全以及没有后续写操作,必须先锁表。</strong></p>
<pre><code>mysql&gt; flush tables with read lock;
</code></pre>
<p>注意,这次的<strong>锁表会导致写阻塞以及innodb的commit操作。</strong></p>
<p>然后查看binlog的坐标。</p>
<pre><code>mysql&gt; show master status; # 为了排版,简化了输出结果
+-------------------+----------+--------------+--------+--------+
| File | Position | Binlog_Do_DB | ...... | ...... |
+-------------------+----------+--------------+--------+--------+
| master-bin.000001 | 623 | | | |
+-------------------+----------+--------------+--------+--------+
</code></pre>
<p>记住master-bin.000001和623。</p>
<h3>4.2.2 备份master数据到slave上</h3>
<p>下面给出3种备份方式以及对应slave的恢复方法。建议备份所有库到slave上如果要筛选一部分数据库或表进行复制应该在slave上筛选(筛选方式见后文<a href="https://www.cnblogs.com/f-ck-need-u/p/9155003.html#blog6.1">筛选要复制的库和表</a>)而不应该在master的备份过程中指定。</p>
<ul>
<li><strong>方式一冷备份直接cp。这种情况只适用于没有新写入操作。严谨一点只适合拷贝完成前master不能有写入操作。</strong></li>
</ul>
<ol>
<li>如果要复制所有库那么直接拷贝整个datadir。</li>
<li>如果要复制的是某个或某几个库直接拷贝相关目录即可。但注意这种冷备份的方式只适合MyISAM表和开启了<code>innodb_file_per_table=ON</code>的InnoDB表。如果没有开启该变量innodb表使用公共表空间无法直接冷备份。</li>
<li>如果要冷备份innodb表最安全的方法是先关闭master上的mysql而不是通过表锁。</li>
</ol>
<p>所以,<strong>如果没有涉及到innodb表那么在锁表之后可以直接冷拷贝。最后释放锁。</strong></p>
<pre><code>mysql&gt; flush tables with read lock;
mysql&gt; show master status; # 为了排版,简化了输出结果
+-------------------+----------+--------------+--------+--------+
| File | Position | Binlog_Do_DB | ...... | ...... |
+-------------------+----------+--------------+--------+--------+
| master-bin.000001 | 623 | | | |
+-------------------+----------+--------------+--------+--------+
shell&gt; rsync -avz /data 192.168.100.150:/
mysql&gt; unlock tables;
</code></pre>
<p>此处实验,假设要备份的是整个实例,因为<strong>涉及到了innodb表所以建议关闭MySQL</strong>。因为是冷备份所以slave上也应该关闭MySQL。</p>
<pre><code># master和slave上都执行
shell&gt; mysqladmin -uroot -p shutdown
</code></pre>
<p>然后将整个datadir拷贝到slave上(当然有些文件是不用拷贝的比如master上的binlog、mysql库等)。</p>
<pre><code>
# 将master的datadir(/data)拷贝到slave的datadir(/data)
shell&gt; rsync -avz /data 192.168.100.150:/
</code></pre>
<p>需要注意在冷备份的时候需要将备份到目标主机上的DATADIR/auto.conf删除这个文件中记录的是mysql server的UUID而master和slave的UUID必须不能一致。</p>
<p>然后重启master和slave。因为重启了master所以binlog已经滚动了不过这次不用再查看binlog坐标因为重启造成的binlog日志移动不会影响slave。</p>
<ul>
<li><strong>方式二使用mysqldump进行备份恢复。</strong></li>
</ul>
<p>这种方式简单的多而且对于innodb表很适用但是slave上恢复时速度慢因为恢复时数据全是通过insert插入的。因为mysqldump可以进行定时点恢复甚至记住binlog的坐标所以无需再手动获取binlog的坐标。</p>
<pre><code>shell&gt; mysqldump -uroot -p --all-databases --master-data=2 &gt;dump.db
</code></pre>
<p>注意,<code>--master-data</code>选项将再dump.db中加入<code>change master to</code>相关的语句值为2时<code>change master to</code>语句是注释掉的值为1或者没有提供值时这些语句是直接激活的。同时<code>--master-data</code>会锁定所有表(如果同时使用了<code>--single-transaction</code>,则不是锁所有表,详细内容请参见<a href="https://www.cnblogs.com/f-ck-need-u/p/9013458.html">mysqldump</a>)。</p>
<p>因此可以直接从dump.db中获取到binlog的坐标。<strong>记住这个坐标。</strong></p>
<pre><code>[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="582a37372c18202d3d2031">[email&#160;protected]</a> ~]# grep -i -m 1 'change master to' dump.db
-- CHANGE MASTER TO MASTER_LOG_FILE='master-bin.000002', MASTER_LOG_POS=154;
</code></pre>
<p>然后将dump.db拷贝到slave上使用mysql执行dump.db脚本即可。也可以直接在master上远程连接到slave上执行。例如</p>
<pre><code>shell&gt; mysql -uroot -p -h 192.168.100.150 -e 'source dump.db'
</code></pre>
<ul>
<li><strong>方式三使用xtrabackup进行备份恢复。</strong></li>
</ul>
<p>这是三种方式中最佳的方式安全性高、速度快。因为xtrabackup备份的时候会记录master的binlog的坐标因此也无需手动获取binlog坐标。</p>
<p>xtrabackup详细的备份方法见<a href="https://www.cnblogs.com/f-ck-need-u/p/9018716.html">xtrabackup</a></p>
<p>注意master和slave上都安装percona-xtrabackup。</p>
<p>以全备份为例:</p>
<pre><code>innobackupex -u root -p /backup
</code></pre>
<p>备份完成后,在/backup下生成一个以时间为名称的目录。其内文件如下</p>
<pre><code>[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8cfee3e3f8ccf4f9e9f4e5">[email&#160;protected]</a> ~]# ll /backup/2018-05-29_04-12-15
total 77872
-rw-r----- 1 root root 489 May 29 04:12 backup-my.cnf
drwxr-x--- 2 root root 4096 May 29 04:12 backuptest
-rw-r----- 1 root root 1560 May 29 04:12 ib_buffer_pool
-rw-r----- 1 root root 79691776 May 29 04:12 ibdata1
drwxr-x--- 2 root root 4096 May 29 04:12 mysql
drwxr-x--- 2 root root 4096 May 29 04:12 performance_schema
drwxr-x--- 2 root root 12288 May 29 04:12 sys
-rw-r----- 1 root root 22 May 29 04:12 xtrabackup_binlog_info
-rw-r----- 1 root root 115 May 29 04:12 xtrabackup_checkpoints
-rw-r----- 1 root root 461 May 29 04:12 xtrabackup_info
-rw-r----- 1 root root 2560 May 29 04:12 xtrabackup_logfile
</code></pre>
<p>其中xtrabackup_binlog_info中记录了binlog的坐标。<strong>记住这个坐标。</strong></p>
<pre><code>[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0d7f6262794d7578687564">[email&#160;protected]</a> ~]# cat /backup/2018-05-29_04-12-15/xtrabackup_binlog_info
master-bin.000002 154
</code></pre>
<p>然后将备份的数据执行&quot;准备&quot;阶段。这个阶段不要求连接mysql因此不用给连接选项。</p>
<pre><code>innobackupex --apply-log /backup/2018-05-29_04-12-15
</code></pre>
<p>最后,将/backup目录拷贝到slave上进行恢复。恢复的阶段就是向MySQL的datadir拷贝。但注意xtrabackup恢复阶段要求datadir必须为空目录。否则报错</p>
<pre><code>[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="60120f0f14201815051809">[email&#160;protected]</a> ~]# innobackupex --copy-back /backup/2018-05-29_04-12-15/
180529 23:54:27 innobackupex: Starting the copy-back operation
IMPORTANT: Please check that the copy-back run completes successfully.
At the end of a successful copy-back run innobackupex
prints &quot;completed OK!&quot;.
innobackupex version 2.4.11 based on MySQL server 5.7.19 Linux (x86_64) (revision id: b4e0db5)
Original data directory /data is not empty!
</code></pre>
<p>所以停止slave的mysql并清空datadir。</p>
<pre><code>service mysqld stop
rm -rf /data/*
</code></pre>
<p>恢复时使用的模式是&quot;--copy-back&quot;,选项后指定要恢复的源备份目录。恢复时因为不需要连接数据库,所以不用指定连接选项。</p>
<pre><code>1
2
[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="ed9f828299ad9598889584">[email&#160;protected]</a> ~]# innobackupex --copy-back /backup/2018-05-29_04-12-15/
180529 23:55:53 completed OK!
</code></pre>
<p>恢复完成后MySQL的datadir的文件的所有者和属组是innobackupex的调用者所以需要改回mysql.mysql。</p>
<pre><code>shell&gt; chown -R mysql.mysql /data
</code></pre>
<p>启动slave并查看恢复是否成功。</p>
<pre><code>shell&gt; service mysqld start
shell&gt; mysql -uroot -p -e 'select * from backuptest.num_isam limit 10;'
+----+
| n |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+----+
</code></pre>
<h2>4.3 slave开启复制</h2>
<p>经过前面的一番折腾总算是把该准备的数据都准备到slave上也获取到master上binlog的坐标(154)。现在还欠东风连接master。</p>
<p>连接master时需要使用<code>change master to</code>提供连接到master的连接选项包括user、port、password、binlog、position等。</p>
<pre><code>mysql&gt; change master to
master_host='192.168.100.20',
master_port=3306,
master_user='repl',
master_password='<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f9a9b98a8a8e968b9dc8">[email&#160;protected]</a>!',
master_log_file='master-bin.000002',
master_log_pos=154;
</code></pre>
<p>完整的<code>change master to</code>语法如下:</p>
<pre><code>CHANGE MASTER TO option [, option] ...
option:
| MASTER_HOST = 'host_name'
| MASTER_USER = 'user_name'
| MASTER_PASSWORD = 'password'
| MASTER_PORT = port_num
| MASTER_LOG_FILE = 'master_log_name'
| MASTER_LOG_POS = master_log_pos
| MASTER_AUTO_POSITION = {0|1}
| RELAY_LOG_FILE = 'relay_log_name'
| RELAY_LOG_POS = relay_log_pos
| MASTER_SSL = {0|1}
| MASTER_SSL_CA = 'ca_file_name'
| MASTER_SSL_CAPATH = 'ca_directory_name'
| MASTER_SSL_CERT = 'cert_file_name'
| MASTER_SSL_CRL = 'crl_file_name'
| MASTER_SSL_CRLPATH = 'crl_directory_name'
| MASTER_SSL_KEY = 'key_file_name'
| MASTER_SSL_CIPHER = 'cipher_list'
| MASTER_SSL_VERIFY_SERVER_CERT = {0|1}
</code></pre>
<p>然后启动IO线程和SQL线程。可以一次性启动两个也可以分开启动。</p>
<pre><code># 一次性启动、关闭
start slave;
stop slave;
# 单独启动
start slave io_thread;
start slave sql_thread;
</code></pre>
<p>至此复制就已经可以开始工作了。当master写入数据slave就会从master处进行复制。</p>
<p>例如在master上新建一个表然后去slave上查看是否有该表。因为是DDL语句它会写二进制日志所以它也会复制到slave上。</p>
<h2>4.4 查看slave的信息</h2>
<p><code>change master to</code>在slave的datadir下就会生成master.info文件和relay-log.info文件这两个文件随着复制的进行其内数据会随之更新。</p>
<h3>4.4.1 master.info</h3>
<p>master.info文件记录的是<strong>IO线程相关的信息</strong>也就是连接master以及读取master binlog的信息。通过这个文件下次连接master时就不需要再提供连接选项。</p>
<p>以下是master.info的内容每一行的意义见<a href="https://dev.mysql.com/doc/refman/5.7/en/slave-logs-status.html">官方手册</a></p>
<pre><code>[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="52203d3d26122a27372a3b">[email&#160;protected]</a> ~]# cat /data/master.info
25 # 本文件的行数
master-bin.000002 # IO线程正从哪个master binlog读取日志
154 # IO线程读取到master binlog的位置
192.168.100.20 # master_host
repl # master_user
<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e4b4a49797938b9680d5">[email&#160;protected]</a>! # master_password
3306 # master_port
60 # master_retryslave重连master的超时时间(单位秒)
0
0
30.000
0
86400
0
</code></pre>
<h3>4.4.2 relay-log.info</h3>
<p>relay-log.info文件中记录的是<strong>SQL线程相关的信息</strong>。以下是relay-log.info文件的内容每一行的意义见<a href="https://dev.mysql.com/doc/refman/5.7/en/slave-logs-status.html">官方手册</a></p>
<pre><code>[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="fb8994948fbb838e9e8392">[email&#160;protected]</a> ~]# cat /data/relay-log.info
7 # 本文件的行数
./slave-bin.000001 # 当前SQL线程正在读取的relay-log文件
4 # SQL线程已执行到的relay log位置
master-bin.000002 # SQL线程最近执行的操作对应的是哪个master binlog
154 # SQL线程最近执行的操作对应的是master binlog的哪个位置
0 # slave上必须落后于master多长时间
0 # 正在运行的SQL线程数
1 # 一种用于内部信息交流的ID目前值总是1
</code></pre>
<h3>4.4.3 show slave status</h3>
<p>在slave上执行<code>show slave status</code>可以查看slave的状态信息。信息非常多每个字段的详细意义可参见<a href="https://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html">官方手册</a></p>
<pre><code>mysql&gt; show slave status\G
*************************** 1. row ***************************
Slave_IO_State: # slave上IO线程的状态来源于show processlist
Master_Host: 192.168.100.20
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master-bin.000002
Read_Master_Log_Pos: 154
Relay_Log_File: slave-bin.000001
Relay_Log_Pos: 4
Relay_Master_Log_File: master-bin.000002
Slave_IO_Running: No # IO线程的状态此处为未运行且未连接状态
Slave_SQL_Running: No # SQL线程的状态此处为未运行状态
Replicate_Do_DB: # 显式指定要复制的数据库
Replicate_Ignore_DB: # 显式指定要忽略的数据库
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table: # 以通配符方式指定要复制的表
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 154
Relay_Log_Space: 154
Until_Condition: None # start slave语句中指定的until条件
# 例如读取到哪个binlog位置就停止
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: NULL # SQL线程执行过的位置比IO线程慢多少
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 0 # master的server id
Master_UUID:
Master_Info_File: /data/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: # slave SQL线程的状态
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.01 sec)
</code></pre>
<p>因为太长,后面再列出<code>show slave status</code>时,将裁剪一些意义不大的行。</p>
<p>再次回到上面<code>show slave status</code>的信息。除了那些描述IO线程、SQL线程状态的行还有几个log_file和pos相关的行如下所列。</p>
<pre><code> Master_Log_File: master-bin.000002
Read_Master_Log_Pos: 154
Relay_Log_File: slave-bin.000001
Relay_Log_Pos: 4
Relay_Master_Log_File: master-bin.000002
Exec_Master_Log_Pos: 154
</code></pre>
<p>理解这几行的意义至关重要,前面因为排版限制,描述看上去有些重复。所以这里完整地描述它们:</p>
<ul>
<li><code>Master_Log_File</code>IO线程正在读取的master binlog</li>
<li><code>Read_Master_Log_Pos</code>IO线程已经读取到master binlog的哪个位置</li>
<li><code>Relay_Log_File</code>SQL线程正在读取和执行的relay log</li>
<li><code>Relay_Log_Pos</code>SQL线程已经读取和执行到relay log的哪个位置</li>
<li><code>Relay_Master_Log_File</code>SQL线程最近执行的操作对应的是哪个master binlog</li>
<li><code>Exec_Master_Log_Pos</code>SQL线程最近执行的操作对应的是master binlog的哪个位置。</li>
</ul>
<p>所以,(Relay_Master_Log_File, Exec_Master_log_Pos)构成一个坐标这个坐标表示slave上已经将master上的哪些数据重放到自己的实例中它可以用于下一次<code>change master to</code>时指定的binlog坐标。</p>
<p>与这个坐标相对应的是slave上SQL线程的relay log坐标(Relay_Log_File, Relay_Log_Pos)。这两个坐标位置不同,但它们对应的数据是一致的。</p>
<p>最后还有一个延迟参数<code>Seconds_Behind_Master</code>需要说明一下它的本质意义是SQL线程比IO线程慢多少。如果master和slave之间的网络状况优良那么slave的IO线程读速度和master写binlog的速度基本一致所以这个参数也用来描述&quot;SQL线程比master慢多少&quot;也就是说slave比master少多少数据只不过衡量的单位是秒。但需要注意这个参数的描述并不标准只有在网速很好的时候做个大概估计很多种情况下它的值都是0即使SQL线程比IO线程慢了很多也是如此。</p>
<h3>4.4.4 slave信息汇总</h3>
<p>上面的master.info、relay-log.info和<code>show slave status</code>的状态都是刚连接上master还未启动IO thread、SQL thread时的状态。下面将显示已经进行一段正在执行复制的slave状态。</p>
<p>首先查看启动io thread和sql thread后的状态。使用<code>show processlist</code>查看即可。</p>
<pre><code>mysql&gt; start slave;
mysql&gt; show processlist; # slave上的信息为了排版简化了输出
+----+-------------+---------+--------------------------------------------------------+
| Id | User | Command | State |
+----+-------------+---------+--------------------------------------------------------+
| 4 | root | Sleep | |
| 7 | root | Query | starting |
| 8 | system user | Connect | Waiting for master to send event |
| 9 | system user | Connect | Slave has read all relay log; waiting for more updates |
+----+-------------+---------+--------------------------------------------------------+
</code></pre>
<p>可以看到:</p>
<ul>
<li><code>Id=8</code>的线程负责连接master并读取binlog它是IO 线程,它的状态指示&quot;等待master发送更多的事件&quot;</li>
<li><code>Id=9</code>的线程负责读取relay log它是SQL线程它的状态指示&quot;已经读取了所有的relay log&quot;</li>
</ul>
<p>再看看此时master上的信息。</p>
<pre><code>mysql&gt; show processlist; # master上的信息为了排版经过了修改
+----+------+-----------------------+-------------+--------------------------------------+
| Id | User | Host | Command | State |
+----+------+-----------------------+-------------+--------------------------------------+
| 4 | root | localhost | Query | starting |
|----|------|-----------------------|-------------|--------------------------------------|
| 16 | repl | 192.168.100.150:39556 | Binlog Dump | Master has sent all binlog to slave; |
| | | | | waiting for more updates |
+----+------+-----------------------+-------------+--------------------------------------+
</code></pre>
<p>master上有一个<code>Id=16</code>的binlog dump线程该线程的用户是repl。它的状态指示&quot;已经将所有的binlog发送给slave了&quot;</p>
<p>现在在master上执行一个长事件以便查看slave上的状态信息。</p>
<p>仍然使用前面插入数值辅助表的存储过程,这次分别向两张表中插入一亿条数据(尽管去抽烟、喝茶,够等几分钟的。如果机器性能不好,请大幅减少插入的行数)。</p>
<pre><code>call proc_num1(100000000);
call proc_num2(100000000);
</code></pre>
<p>然后去slave上查看信息如下。因为太长已经裁剪了一部分没什么用的行。</p>
<pre><code>mysql&gt; show slave status\G
mysql: [Warning] Using a password on the command line interface can be insecure.
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.100.20
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: master-bin.000003
Read_Master_Log_Pos: 512685413
Relay_Log_File: slave-bin.000003
Relay_Log_Pos: 336989434
Relay_Master_Log_File: master-bin.000003
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Exec_Master_Log_Pos: 336989219
Slave_SQL_Running_State: Reading event from the relay log
</code></pre>
<p>从中获取到的信息有:</p>
<ol>
<li>IO线程的状态</li>
<li>SQL线程的状态</li>
<li>IO线程读取到master binlog的哪个位置512685413</li>
<li>SQL线程已经执行到relay log的哪个位置336989434</li>
<li>SQL线程执行的位置对应于master binlog的哪个位置336989219</li>
</ol>
<p>可以看出IO线程比SQL线程超前了很多很多所以SQL线程比IO线程的延迟较大。</p>
<h2>4.5 MySQL复制如何实现断开重连</h2>
<p>很多人以为<code>change master to</code>语句是用来连接master的实际上这种说法是错的。连接master是IO线程的事情<code>change master to</code>只是为IO线程连接master时提供连接参数。</p>
<p>如果slave从来没连接过master那么必须使用<code>change master to</code>语句来生成IO线程所需要的信息这些信息记录在master.info中。这个文件是<code>change master to</code>成功之后立即生成的以后启动IO线程时IO线程都会自动读取这个文件来连接master不需要先执行<code>change master to</code>语句。</p>
<p>例如,可以随时<code>stop slave</code>来停止复制线程,然后再随时<code>start slave</code>只要master.info存在且没有人为修改过它IO线程就一定不会出错。这是因为master.info会随着IO线程的执行而更新无论读取到master binlog的哪个位置都会记录下这个位置如此一来IO线程下次启动的时候就知道从哪里开始监控master binlog。</p>
<p>前面还提到一个文件:<code>relay-log.info</code>文件。这个文件中记录的是SQL线程的相关信息包括读取、执行到relay log的哪个位置刚重放的数据对应master binlog的哪个位置。随着复制的进行这个文件的信息会即时改变。所以通过relay-log.info下次SQL线程启动的时候就能知道从relay log的哪个地方继续读取、执行。</p>
<p>如果不小心把relay log文件删除了SQL线程可能会丢失了一部分相比IO线程延迟的数据。这时候只需将relay-log.info中第4、5行记录的&quot;SQL线程刚重放的数据对应master binlog的坐标&quot;手动修改到master.info中即可这样IO线程下次连接master就会从master binlog的这个地方开始监控。当然也可以将这个坐标作为<code>change master to</code>的坐标来修改master.info。</p>
<p>此外当mysql实例启动时默认会自动<code>start slave</code>也就是MySQL一启动就自动开启复制线程。如果想要禁止这种行为在配置文件中加上</p>
<pre><code>[mysqld]
skip-slave-start
</code></pre>
<p>4.6 一些变量</p>
<p>默认情况下slave连接到master后会在slave的datadir下生成master.info和relay-log.info文件但是这是可以通过设置变量来改变的。</p>
<ul>
<li><code>master-info-repository={TABLE|FILE}</code>master的信息是记录到文件master.info中还是记录到表mysql.slave_master_info中。默认为file。</li>
<li><code>relay-log-info-repository={TABLE|FILE}</code>slave的信息是记录到文件relay-log.info中还是记录到表mysql.slave_relay_log_info中。默认为file。</li>
</ul>
<p>IO线程每次从master复制日志要写入到relay log中但是它是先放在内存的等到一定时机后才会将其刷到磁盘上的relay log文件中。刷到磁盘的时机可以由变量控制。</p>
<p>另外IO线程每次从master复制日志后都会更新master.info的信息也是先更新内存中信息在特定的时候才会刷到磁盘的master.info文件同理SQL线程更新realy-log.info也是一样的。它们是可以通过变量来设置更新时机的。</p>
<ul>
<li><code>sync-relay-log=N</code>设置为大于0的数表示每从master复制N个事件就刷一次盘。设置为0表示依赖于操作系统的sync机制。</li>
<li><code>sync-master-info=N</code>:依赖于<code>master-info-repository</code>的设置如果为file则设置为大于0的值时表示每更新多少次master.info将其写入到磁盘的master.info中设置为0则表示由操作系统来决定何时调用<code>fdatasync()</code>函数刷到磁盘。如果设置为table则设置为大于0的值表示每更新多少次master.info就更新mysql.slave_master_info表一次如果设置为0则表示永不更新该表。</li>
<li><code>sync-relay-log-info=N</code>:同上。</li>
</ul>
<h1>5.一主多从</h1>
<p>一主多从有两种情况,结构图如下。</p>
<p>以下是一主多从的结构图(和一主一从的配置方法完全一致)
<img src="assets/733013-20180528163904784-673253663.png" alt="img" /></p>
<p>以下是一主多从但某slave是另一群MySQL实例的master
<img src="assets/733013-20180528163917913-780164983.png" alt="img" /></p>
<p>配置一主多从时需要考虑一件事slave上是否要开启binlog? 如果不开启slave的binlog性能肯定要稍微好一点。但是开启了binlog后可以通过slave来备份数据也可以在master宕机时直接将slave切换为新的master。此外如果是上面第二种主从结构这台slave必须开启binlog。可以将某台或某几台slave开启binlog并在mysql动静分离的路由算法上稍微减少一点到这些slave上的访问权重。</p>
<p>上面第一种一主多从的结构没什么可解释的它和一主一从的配置方式完全一样但是可以考虑另一种情况向现有主从结构中添加新的slave。所以稍后先介绍这种添加slave再介绍第二种一主多从的结构。</p>
<h2>5.1 向现有主从结构中添加slave</h2>
<p>官方手册:<a href="https://dev.mysql.com/doc/refman/5.7/en/replication-howto-additionalslaves.html">https://dev.mysql.com/doc/refman/5.7/en/replication-howto-additionalslaves.html</a></p>
<p>例如在前文一主一从的实验环境下添加一台新的slave。</p>
<p>因为新的slave在开始复制前要有master上的基准数据还要有master binlog的坐标。按照前文一主一从的配置方式当然很容易获取这些信息但这样会将master锁住一段时间(因为要备份基准数据)。</p>
<p>深入思考一下其实slave上也有数据还有relay log以及一些仓库文件标记着数据复制到哪个地方。所以完全<strong>可以从slave上获取基准数据和坐标也建议这样做</strong></p>
<p>仍然有三种方法从slave上获取基准数据冷备份、mysqldump和xtrabackup。方法见前文<a href="https://www.cnblogs.com/f-ck-need-u/p/9155003.html#blog4.2">将slave恢复到master指定的坐标</a></p>
<p>其实<strong>临时关闭一个slave对业务影响很小所以我个人建议新添加slave时采用冷备份slave的方式</strong>不仅备份恢复的速度最快配置成slave也最方便这一点和前面配置&quot;一主一从&quot;不一样。但冷备份slave的时候需要注意几点</p>
<ol>
<li>可以考虑将slave1完全shutdown再将整个datadir拷贝到新的slave2上。</li>
<li><strong>建议新的slave2配置文件中的&quot;relay-log&quot;的值和slave1的值完全一致</strong>否则应该手动从slave2的relay-log.info中获取IO线程连接master时的坐标并在slave2上使用<code>change master to</code>语句设置连接参数。
方法很简单,所以不做演示了。</li>
</ol>
<h2>5.2 配置一主多从(从中有从)</h2>
<p>此处实现的一主多从是下面这种结构:</p>
<p><img src="assets/733013-20180528163917913-780164983.png" alt="img" /></p>
<p>这种结构对MySQL复制来说是一个很好的提升性能的方式。对于只有一个master的主从复制结构每多一个slave意味着master多发一部分binlog业务稍微繁忙一点时这种压力会加剧。而这种一个主master、一个或多个辅助master的主从结构非常有助于MySQL集群的伸缩性对压力的适应性也很强。</p>
<blockquote>
<p>除上面一主多从、从中有从的方式可提升复制性能还有几种提升MySQL复制性能的方式</p>
<blockquote>
<ol>
<li>将不同数据库复制到不同slave上。</li>
<li>可以将master上的事务表(如InnoDB)复制为slave上的非事务表(如MyISAM)这样slave上回放的速度加快查询数据的速度在一定程度上也会提升。</li>
</ol>
</blockquote>
</blockquote>
<p>回到这种主从结构它有些不同master只负责传送日志给slave1、slave2和slave3slave 2_1和slave 2_2的日志由slave2负责传送所以slave2上也必须要开启binlog选项。此外还必须开启一个选项<code>--log-slave-updates</code>让slave2能够在重放relay log时也写自己的binlog否则slave2的binlog仅接受人为的写操作。</p>
<p><strong>slave能否进行写操作重放relay log的操作是否会记录到slave的binlog中</strong></p>
<ol>
<li>在slave上没有开启<code>read-only</code>选项(只读变量)时任何有写权限的用户都可以进行写操作这些操作都会记录到binlog中。注意<strong>read-only选项对具有super权限的用户以及SQL线程执行的重放写操作无效</strong>。默认这个选项是关闭的。</li>
</ol>
<pre><code>mysql&gt; show variables like &quot;read_only&quot;;
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| read_only | OFF |
+---------------+-------+
</code></pre>
<ol>
<li>在slave上没有开启<code>log-slave-updates</code>和binlog选项时重放relay log不会记录binlog。</li>
</ol>
<p><strong>所以如果slave2要作为某些slave的master那么在slave2上必须要开启log-slave-updates和binlog选项。为了安全和数据一致性在slave2上还应该启用read-only选项。</strong></p>
<p>环境如下:</p>
<p><img src="assets/733013-20180608100823680-1104661841.png" alt="img" /></p>
<p>以下是master、slave1和slave2上配置文件内容。</p>
<pre><code># master上的配置
[mysqld]
datadir=/data
socket=/data/mysql.sock
server_id=100
sync-binlog=1
log_bin=master-bin
log-error=/data/err.log
pid-file=/data/mysqld.pid
# slave1上的配置
[mysqld]
datadir=/data
socket=/data/mysql.sock
server_id=111
relay-log=slave-bin
log-error=/data/err.log
pid-file=/data/mysqld.pid
log-slave-updates # 新增配置
log-bin=master-slave-bin # 新增配置
read-only=ON # 新增配置
# slave2上的配置
[mysqld]
datadir=/data
socket=/data/mysql.sock
server_id=123
relay-log=slave-bin
log-error=/data/err.log
pid-file=/data/mysqld.pid
read-only=ON
</code></pre>
<p>因为slave2目前是全新的实例所以先将slave1的基准数据备份到slave2。由于slave1自身就是slave临时关闭一个slave对业务影响很小所以直接采用冷备份slave的方式。</p>
<pre><code># 在slave2上执行
shell&gt; mysqladmin -uroot -p shutdown
# 在slave1上执行
shell&gt; mysqladmin -uroot -p shutdown
shell&gt; rsync -az --delete /data 192.168.100.19:/
shell&gt; service mysqld start
</code></pre>
<p><strong>冷备份时,以下几点千万注意</strong></p>
<ol>
<li>
<p>因为slave2是slave1的从所以</p>
<p>在启动MySQL前必须将备份到slave2上的和复制有关的文件都删除</p>
<p>。包括:</p>
<ul>
<li>(1).master.info。除非配置文件中指定了<code>skip-slave-start</code>否则slave2将再次连接到master并作为master的slave。</li>
<li>(2).relay-log.info。因为slave1启动后会继续执行relay log中的内容(如果有未执行的)这时slave1会将这部分写入binlog并传送到slave2。</li>
<li>(3).删除relay log文件。其实不是必须删除但建议删除。</li>
<li>(4).删除relay log index文件。</li>
<li>(5).删除DATADIR/auto.conf。这个文件必须删除因为这里面保留了mysql server的UUID而master和slave的UUID必须不能一致。在启动mysql的时候如果没有这个文件会自动生成自己的UUID并保存到auto.conf中。</li>
</ul>
</li>
<li>
<p>检查slave1上从master复制过来的专门用于复制的用户<code>repl</code>是否允许slave2连接。如果不允许应该去master上修改这个用户。</p>
</li>
<li>
<p>因为slave1是刚开启的binlog所以slave2连接slave1时的binlog position应该指定为4。即使slave1不是刚开启的binlog它在重启后也会滚动binlog。</p>
</li>
</ol>
<p>所以在slave2上继续操作</p>
<pre><code>shell&gt; ls /data
auto.cnf ib_buffer_pool ib_logfile1 performance_schema slave-bin.000005
backuptest ibdata1 master.info relay-log.info slave-bin.index
err.log ib_logfile0 mysql slave-bin.000004 sys
shell&gt; rm -f /data/{master.info,relay-log.info,auto.conf,slave-bin*}
shell&gt; service mysqld start
</code></pre>
<p>最后连上slave2启动复制线程。</p>
<pre><code>shell&gt; mysql -uroot -p
mysql&gt; change master to
master_host='192.168.100.150',
master_port=3306,
master_user='repl',
master_password='<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="adfdeddededac2dfc99c">[email&#160;protected]</a>!',
master_log_file='master-slave-bin.000001',
master_log_pos=4;
mysql&gt; start slave;
mysql&gt; show slave status\G
</code></pre>
<h1>6.MySQL复制中一些常用操作</h1>
<h2>6.1 筛选要复制的库和表</h2>
<p>默认情况下slave会复制master上所有库。可以指定以下变量显式指定要复制的库、表和要忽略的库、表也可以将其写入配置文件。</p>
<pre><code> Replicate_Do_DB: 要复制的数据库
Replicate_Ignore_DB: 不复制的数据库
Replicate_Do_Table: 要复制的表
Replicate_Ignore_Table: 不复制的表
Replicate_Wild_Do_Table: 通配符方式指定要复制的表
Replicate_Wild_Ignore_Table: 通配符方式指定不复制的表
</code></pre>
<p>如果要指定列表,则多次使用这些变量进行设置。</p>
<p>需要注意的是,<strong>尽管显式指定了要复制和忽略的库或者表但是master还是会将所有的binlog传给slave并写入到slave的relay log中真正负责筛选的slave上的SQL线程</strong></p>
<p>另外如果slave上开启了binlogSQL线程读取relay log后会将所有的事件都写入到自己的binlog中只不过对于那些被忽略的事件只记录相关的事务号等信息不记录事务的具体内容。所以如果之前设置了被忽略的库或表后来取消忽略后它们在取消忽略以前的变化是不会再重放的特别是基于gtid的复制会严格比较binlog中的gtid。</p>
<p>总之使用筛选的时候应该多多考虑是否真的要筛选,是否是永久筛选。</p>
<h2>6.2 reset slave和reset master</h2>
<p><code>reset slave</code>会删除master.info/relay-log.info和relay log然后新生成一个relay log。但是<code>change master to</code>设置的连接参数还在内存中保留着所以此时可以直接start slave并根据内存中的<code>change master to</code>连接参数复制日志。</p>
<p><code>reset slave all</code>除了删除<code>reset slave</code>删除的东西,还删除内存中的<code>change master to</code>设置的连接信息。</p>
<p><code>reset master</code>会删除master上所有的二进制日志并新建一个日志。在正常运行的主从复制环境中执行<code>reset master</code>很可能导致异常状况。所以建议使用purge来删除某个时间点之前的日志(应该保证只删除那些已经复制完成的日志)。</p>
<h2>6.3 show slave hosts</h2>
<p>如果想查看master有几个slave的信息可以使用<code>show slave hosts</code>。以下为某个master上的结果</p>
<pre><code>mysql&gt; show slave hosts;
+-----------+------+------+-----------+--------------------------------------+
| Server_id | Host | Port | Master_id | Slave_UUID |
+-----------+------+------+-----------+--------------------------------------+
| 111 | | 3306 | 11 | ff7bb057-2466-11e7-8591-000c29479b32 |
| 1111 | | 3306 | 11 | 9b119463-24d2-11e7-884e-000c29867ec2 |
+-----------+------+------+-----------+--------------------------------------+
</code></pre>
<p>可以看到该show中会显示server-id、slave的主机地址和端口号、它们的master_id以及这些slave独一无二的uuid号。</p>
<p>其中show结果中的host显示结果是由slave上的变量report_host控制的端口是由report_port控制的。</p>
<p>例如在slave2上修改其配置文件添加report-host项后重启MySQL服务。</p>
<pre><code>[mysqld]
report_host=192.168.100.19
</code></pre>
<p>在slave1(前文的实验环境slave1是slave2的master)上查看host已经显示为新配置的项。</p>
<pre><code>mysql&gt; show slave hosts;
+-----------+----------------+------+-----------+--------------------------------------+
| Server_id | Host | Port | Master_id | Slave_UUID |
+-----------+----------------+------+-----------+--------------------------------------+
| 111 | 192.168.100.19 | 3306 | 11 | ff7bb057-2466-11e7-8591-000c29479b32 |
| 1111 | | 3306 | 11 | 9b119463-24d2-11e7-884e-000c29867ec2 |
+-----------+----------------+------+-----------+--------------------------------------+
</code></pre>
<h2>6.4 多线程复制</h2>
<p>在老版本中只有一个SQL线程读取relay log并重放。重放的速度肯定比IO线程写relay log的速度慢非常多导致SQL线程非常繁忙<strong>实现到从库上延迟较大</strong><strong>没错,多线程复制可以解决主从延迟问题,且使用得当的话效果非常的好(关于主从复制延迟,是生产环境下最常见的问题之一,且没有很好的办法来避免。后文稍微介绍了一点方法)</strong></p>
<p>在MySQL 5.6中引入了多线程复制(multi-thread slave简称MTS),这个<strong>多线程指的是多个SQL线程IO线程还是只有一个</strong>。当IO线程将master binlog写入relay log中后一个称为&quot;多线程协调器(multithreaded slave coordinator)&quot;会对多个SQL线程进行调度让它们按照一定的规则去执行relay log中的事件。</p>
<p>**需要谨记于心的是,如果对多线程复制没有了解的很透彻,千万不要在生产环境中使用多线程复制。**它的确带来了一些复制性能的提升,并且能解决主从超高延迟的问题,但随之而来的是很多的&quot;疑难杂症&quot;,这些&quot;疑难杂症&quot;并非是bug只是需要多多了解之后才知道为何会出现这些问题以及如何解决这些问题。稍后会简单介绍一种多线程复制问题gaps。</p>
<p>通过全局变量<code>slave-parallel-workers</code>控制SQL线程个数设置为非0正整数N表示多加N个SQL线程加上原有的共N+1个SQL线程。默认为0表示不加任何SQL线程即关闭多线程功能。</p>
<pre><code>mysql&gt; show variables like &quot;%parallel%&quot;;
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| slave_parallel_workers | 0 |
+------------------------+-------+
</code></pre>
<p>显然多线程只有在slave上开启才有效因为只有slave上才有SQL线程。另外设置了该全局变量需要<strong>重启SQL线程</strong>才生效否则内存中还是只有一个SQL线程。</p>
<p>例如初始时slave上的processlist如下</p>
<p><img src="assets/733013-20180606081617238-1542051622.png" alt="img" /></p>
<p>设置<code>slave_parallel_workers=2</code></p>
<pre><code>mysql&gt; set @@global.slave_parallel_workers=2;
mysql&gt; stop slave sql_thread;
msyql&gt; start slave sql_thread;
mysql&gt; show full processlist;
</code></pre>
<p><img src="assets/733013-20180606081705265-1189641192.png" alt="img" /></p>
<p>可见多出了两个线程,其状态信息是&quot;Waiting for an event from Coordinator&quot;</p>
<p>虽然是多个SQL线程但是复制时每个库只能使用一个线程(默认情况下,可以通过<code>--slave-parallel-type</code>修改并行策略)因为如果一个库可以使用多个线程多个线程并行重放relay log可能导致数据错乱。所以应该设置线程数等于或小于要复制的库的数量设置多了无效且浪费资源。</p>
<h3>6.4.1 多线程复制带来的不一致问题</h3>
<p>虽然多线程复制带来了一定的复制性能提升,但它也带来了很多问题,最严重的是一致性问题。完整的内容见<a href="https://dev.mysql.com/doc/refman/8.0/en/replication-features-transaction-inconsistencies.html">官方手册</a>。此处介绍其中一个最重要的问题。</p>
<p><strong>关于多线程复制最常见也是开启多线程复制前最需要深入了解的问题是由于多个SQL线程同时执行relay log中的事务这使得slave上提交事务的顺序很可能和master binlog中记录的顺序不一致(除非指定变量slave_preserve_commit_order=1)。</strong>(注意这里说的是事务而不是事件。因为MyISAM的binlog顺序无所谓只要执行完了就正确而且多线程协调器能够协调好这些任务。所以只需考虑innodb基于事务的binlog)</p>
<p>举个简单的例子master上事务A先于事务B提交到了slave上因为多SQL线程的原因可能事务B提交了事务A却还没提交。</p>
<p>是否还记得<code>show slave status</code>中的<code>Exec_master_log_pos</code>代表的意义它表示SQL线程最近执行的事件对应的是master binlog中的哪个位置。问题由此而来。通过<code>show slave status</code>我们看到已经执行事件对应的坐标它前面可能还有事务没有执行。而在relay log中事务B记录的位置是在事务A之后的(和master一样)于是事务A和事务B之间可能就存在一个孔洞(gap)这个孔洞是事务A剩余要执行的操作。</p>
<p>正常情况下,多线程协调器记录了一切和多线程复制相关的内容,它能识别这种孔洞(通过打低水位标记low-watermark),也能正确填充孔洞。<strong>即使是在存在孔洞的情况下执行stop slave也不会有任何问题因为在停止SQL线程之前它会等待先把孔洞填充完</strong>。但危险因素太多比如突然宕机、突然杀掉mysqld进程等等这些都会导致孔洞持续下去甚至可能因为操作不当而永久丢失这部分孔洞。</p>
<p>那么如何避免这种问题,出现这种问题如何解决?</p>
<p><strong>1.如何避免gap。</strong></p>
<p>前面说了多个SQL线程是通过协调器来调度的。默认情况下可能会出现gap的情况这是因为变量<code>slave_preserve_commit_order</code>的默认值为0。该变量指示协调器是否让每个SQL线程执行的事务按master binlog中的顺序提交。因此将其设置为1然后重启SQL线程即可保证SQL线程按序提交也就不可能会有gap的出现。</p>
<p>当事务B准备先于事务A提交的时候它将一直等待。此时slave的状态将显示</p>
<pre><code>1
2
Waiting for preceding transaction to commit # MySQL 5.7.8之后显示该状态
Waiting for its turn to commit # MySQL 5.7.8之前显示该状态
</code></pre>
<p>尽管不会出现gap<code>show slave status</code><code>Exec_master_log_pos</code>仍可能显示在事务A的坐标之后。</p>
<p>由于开启<code>slave_preserve_commit_order</code>涉及到不少操作它还要求开启slave的binlog<code>--log-bin</code>(因此需要重启mysqld)且开启重放relay log也记录binlog的行为<code>--log-slave-updates</code>,此外,还必须设置多线程的并行策略<code>--slave-parallel-type=LOGICAL_CLOCK</code></p>
<pre><code>shell&gt; mysqladmin -uroot -p shutdown
shell&gt; cat /etc/my.cnf
log_bin=slave-bin
log-slave-updates
slave_parallel_workers=1
slave_parallel_type=LOGICAL_CLOCK
shell&gt;service mysqld start
</code></pre>
<p><strong>2.如何处理已经存在的gap。</strong></p>
<p>方法之一是从master上重新备份恢复到slave上这种方法是处理不当的最后解决办法。</p>
<p>正常的处理方法是,使用<code>START SLAVE [SQL_THREAD] UNTIL SQL_AFTER_MTS_GAPS;</code>它表示SQL线程只有先填充gaps后才能启动。实际上它涉及了两个操作</p>
<ul>
<li>(1).填充gaps</li>
<li>(2).自动停止SQL线程(所以之后需要手动启动SQL线程)</li>
</ul>
<p>一般来说在填充完gaps之后应该先<code>reset slave</code>移除已经执行完的relay log然后再去启动sql_thread。</p>
<h3>6.4.2 多线程复制切换回单线程复制</h3>
<p>多线程的带来的问题不止gaps一种所以没有深入了解多线程的情况下千万不能在生产环境中启用它。如果想将多线程切换回单线程可以执行如下操作</p>
<pre><code>START SLAVE UNTIL SQL_AFTER_MTS_GAPS;
SET @@GLOBAL.slave_parallel_workers = 0;
START SLAVE SQL_THREAD;
</code></pre>
<h2>6.5 slave升级为master的大致操作</h2>
<p>当master突然宕机有时候需要切换到slave将slave提升为新的master。但对于master突然宕机可能造成的数据丢失主从切换是无法解决的它只是尽可能地不间断提供MySQL服务。</p>
<p>假如现在有主服务器M从服务器S1、S2S1作为将来的新的master。</p>
<ol>
<li>在将S1提升为master之前需要保证S1已经将relay log中的事件已经replay完成。即下面两个状态查看语句中SQL线程的状态显示为&quot;Slave has read all relay log; waiting for the slave I/O thread to update it&quot;</li>
</ol>
<pre><code>show slave status;
show processlist;
</code></pre>
<ol>
<li>停止S1上的IO线程和SQL线程然后将S1的binlog清空(要求已启用binlog)。</li>
</ol>
<pre><code>1
2
mysql&gt; stop slave;
mysql&gt; reset master;
</code></pre>
<ol>
<li>在S2上停止IO线程和SQL线程通过<code>change master to</code>修改master的指向为S1然后再启动io线程和SQL线程。</li>
</ol>
<pre><code>mysql&gt; stop slave;
mysql&gt; change master to master_host=S1,...
mysql&gt; start slave;
</code></pre>
<ol>
<li>将应用程序原本指向M的请求修改为指向S1如修改MySQL代理的目标地址。一般会通过MySQL Router、Amoeba、cobar等数据库中间件来实现。</li>
<li>删除S1上的master.info、relay-log.info文件否则下次S1重启服务器会继续以slave角色运行。</li>
<li>将来M重新上线后可以将其配置成S1的slave然后修改应用程序请求的目标列表添加上新上线的M如将M加入到MySQL代理的读目标列表。</li>
</ol>
<p>注意:<code>reset master</code>很重要如果不是基于GTID复制且开启了<code>log-slave-updates</code>选项时S1在应用relay log的时候会将其写入到自己的binlog以后S2会复制这些日志导致重复执行的问题。</p>
<p>其实上面只是提供一种slave升级为Master的解决思路在实际应用中环境可能比较复杂。例如上面的S1是S2的master这时S1如果没有设置为read-only当M宕机时可以不用停止S1也不需要<code>reset master</code>等操作受影响的操作仅仅只是S1一直无法连接M而已但这对业务不会有多大的影响。</p>
<p>相信理解了前面的内容,分析主从切换的思路应该也没有多大问题。</p>
<h2>6.6 指定不复制到slave上的语句</h2>
<p>前面说的<a href="https://www.cnblogs.com/f-ck-need-u/p/9155003.html#blog6.1">筛选要复制的库和表</a>可以用于指定不复制到slave上的库和表但却没有筛选不复制到slave的语句。</p>
<p>但有些特殊情况下可能需要这种功能。例如master上创建专门用于复制的用户repl这种语句其实没有必要复制到slave上甚至出于安全的考虑不应该复制到slave上。</p>
<p>可以使用<code>sql_log_bin</code>变量对此进行设置默认该变量的值为1表示所有语句都写进binlog从而被slave复制走。如果设置为0则之后的语句不会写入binlog从而实现&quot;不复制某些语句到slave&quot;上的功能。</p>
<p>例如屏蔽创建repl用户的语句。</p>
<pre><code>mysql&gt; set sql_log_bin=0;
mysql&gt; create user <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e09285908ca0">[email&#160;protected]</a>'%' identified by '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="2575655656524a574114">[email&#160;protected]</a>!';
mysql&gt; grant replication slave on *.* to <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8ffdeaffe3cf">[email&#160;protected]</a>'%';
mysql&gt; set sql_log_bin=1;
</code></pre>
<p>在使用该变量时默认是会话范围内的变量一定不能设置它的全局变量值否则所有语句都将不写binlog。</p>
<h2>6.7 主从高延迟的解决思路</h2>
<p>slave通过IO线程获取master的binlog并通过SQL线程来应用获取到的日志。因为各个方面的原因经常会出现slave的延迟(即<code>Seconds_Behind_Master</code>的值)非常高(动辄几天的延迟是常见的,几个小时的延迟已经算短的),使得主从状态不一致。</p>
<p>一个很容易理解的延迟示例是假如master串行执行一个大事务需要30分钟那么slave应用这个事务也大约要30分钟从master提交的那一刻开始slave的延迟就是30分钟更极端一点由于binlog的记录时间点是在事务提交时如果这个大事务的日志量很大比如要传输10多分钟那么很可能延迟要达到40分钟左右。而且更严重的是这种延迟具有滚雪球的特性从延迟开始很容易导致后续加剧延迟。</p>
<p>所以第一个优化方式是不要在mysql中使用大事务这是mysql主从优化的第一口诀。</p>
<p>在回归正题要解决slave的高延迟问题先要知道<code>Second_Behind_Master</code>是如何计算延迟的SQL线程比IO线程慢多少(其本质是NOW()减去<code>Exec_Master_Log_Pos</code>处设置的TIMESTAMP)。在主从网络状态良好的情况下IO线程和master的binlog大多数时候都能保持一致(也即是IO线程没有多少延迟除非事务非常大导致二进制日志传输时间久<strong>但mysql优化的一个最基本口诀就是大事务切成小事务</strong>)所以在这种理想状态下可以认为主从延迟说的是slave上的数据状态比master要延迟多少。它的计数单位是秒。</p>
<p>1.<strong>从产生Binlog的master上考虑可以在master上应用group commit的功能并设置参数binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count前者表示延迟多少秒才提交事务后者表示要堆积多少个事务之后再提交。这样一来事务的产生速度降低slave的SQL线程相对就得到缓解</strong></p>
<p>2.<strong>再者从slave上考虑可以在slave上开启多线程复制(MTS)功能让多个SQL线程同时从一个IO线程中取事务进行应用这对于多核CPU来说是非常有效的手段</strong>。但是前面介绍多线程复制的时候说过没有掌握多线程复制的方方面面之前千万不要在生产环境中使用多线程复制要是出现gap问题很让人崩溃。</p>
<p>3.最后从架构上考虑。主从延迟是因为slave跟不上master的速度那么可以考虑对master进行节流控制让master的性能下降从而变相提高slave的能力。这种方法肯定是没人用的但确实是一种方法提供了一种思路比如slave使用性能比master更好的硬件。另一种比较可取的方式是加多个中间slave层(也就是master-&gt;slaves-&gt;slaves)让多个中间slave层专注于复制(也可作为非业务的他用,比如用于备份)。</p>
<p>4.使用组复制或者Galera/PXC的多写节点此外还可以设置相关参数让它们对延迟自行调整。但一般都不需要调整因为有默认设置。</p>
<p>还有比较细致的方面可以降低延迟比如设置为row格式的Binlog要比statement要好因为不需要额外执行语句直接修改数据即可。比如master设置保证数据一致性的日志刷盘规则(sync_binlog/innodb_flush_log_at_trx_commit设置为1)而slave关闭binlog或者设置性能优先于数据一致性的binlog刷盘规则。再比如设置slave的隔离级别使得slave的锁粒度放大不会轻易锁表(多线程复制时避免使用此方法)。还有很多方面,选择好的磁盘,设计好分库分表的结构等等,这些都是直接全局的,实在没什么必要在这里多做解释。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997fe73bc98b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>