MySQL:主从复制(一)

0. 主从复制 首先主从复制是什么?简单来说是让一台MySQL服务器去复制另一台MySQL的数据,使两个服务器的数据保持一致。 这种方式与Redis的主从复制的思路没有太大的出入。如果你对Redis的主从复制感兴趣可以去看看《Redis的主从复制》。那既然Redis和MySQL都采用了复制这种方式,

0. 主从复制

首先主从复制是什么?简单来说是让一台MySQL服务器去复制另一台MySQL的数据,使两个服务器的数据保持一致。

这种方式与Redis的主从复制的思路没有太大的出入。如果你对Redis的主从复制感兴趣可以去看看《Redis的主从复制》。那既然Redis和MySQL都采用了复制这种方式,主从复制所带来的意义是什么呢?

通过复制功能,构建一个或者多个从库,可以提高数据库的高可用性可扩展性,同时实现负载均衡。当主库发生故障时,可以快速的切到其某一个从库,并将该从库提升为主库,因为数据都一样,所以不会影响系统的运行;当MySQL服务器需要扛住更多的读请求时,可以把读请求的流量分流到各个从库上去,写请求则转发给主库,形成读写分离的架构,来提供更好的读扩展和请求的负载均衡。

读写分离的架构应用的其实非常广泛,就比如MySQL,还有Redis,以及我们熟悉的Zookeeper,Zookeeper的Follower收到读请求不会自己处理,而是会将读请求转发给Leader,感兴趣的可以自己下来了解一下,这里就不偏题了。

1. 复制原理

MySQL的主从复制支持两种方式:

  • 基于
  • 基于语句

基于语句的复制在MySQL3.23中就已经有了,而基于行的方式则在5.1中才实现。其本质都是基于主库的binlog来实现的,主库记录binlog,然后从库将binlog在自己的服务器上重放,从而保证了主、从的数据一致性。

1.1 binlog

MySQL中日志分为两个维度,一个是MySQL服务器的,一个是底层存储引擎的。而上文提到的binlog就是属于MySQL服务器的日志,binlog也叫二进制日志,记录了所有对MySQL所做的更改。

基于行、语句的复制方式跟binlog的存储方式有关系。binlog有三种存储格式,分别是Statement、Row和Mixed。

  • Statement 基于语句,只记录对数据做了修改的SQL语句,能够有效的减少binlog的数据量,提高读取、基于binlog重放的性能
  • Row 只记录被修改的行,所以Row记录的binlog日志量一般来说会比Statement格式要多。基于Row的binlog日志非常完整、清晰,记录了所有数据的变动,但是缺点是可能会非常多,例如一条update语句,有可能是所有的数据都有修改;再例如alter table之类的,修改了某个字段,同样的每条记录都有改动。
  • Mixed Statement和Row的结合,怎么个结合法呢。例如像update或者alter table之类的语句修改,采用Statement格式。其余的对数据的修改例如updatedelete采用Row格式进行记录。

为什么会有这么多方式呢?因为Statement只会记录SQL语句,但是并不能保证所有情况下这些语句在从库上能够正确的被重放出来。因为可能顺序不对。

MySQL什么时候会记录binlog呢?是在事务提交的时候,并不是按照语句的执行顺序来记录,当记录完binlog之后,就会通知底层的存储引擎提交事务,所以有可能因为语句顺序错误导致语句出错。

1.2 查看binlog

这里拿MySQL 5.6举例子,binlog默认是处于关闭状态的。我们可以通过命令show variables like '%log_bin%' 来查看关于binlog的配置。

image

默认配置

log_bin代表是否开启了binlog,其默认值为OFF

  • log_bin 代表是否开启了binlog,其默认值为OFF
  • log_bin_basename binlog存储文件的完整名称,会在默认的文件名后面添加上递增的序号,就例如mysql-bin.000001
  • log_bin_index binlog索引文件名称,例如mysql-bin.index
  • sql_log_bin 在binlog开启的时候,可以禁用当前session的binlog

你可以在MySQL中通过命令show binary logs查看所有的binlog文件

image

查看binlog

知道了有哪些文件之后我们可以来看看binlog文件中的内容,可以在MySQL通过show binlog events命令来查看。

show binglog events 查看第一个binlog文件,我们也可以通过in参数来指定,假设我们想看的文件名是mysql-bin.000001,那么可以使用命令show binlog events in 'mysql-bin.000001’来查看指定的binlog文件

image

查看binlog

接下来我们来看看我们在MySQL中的操作所对应的binlog内容分别是什么。

初始化

我们上面提到过,binlog是由一个一个的event组成的。从MySQL 5.0开始,binlog的第一个event都为Format_desc,位于图中的Event_type那一列。可以看到内容为Server ver;5.6.50-log, Binlog ver: 4,说明当前使用的MySQL版本为5.6.50,Binlog的版本是V4。

创建数据库

然后我创建了一个名为student的DB,其Event_type是Query,这个event的内容为CREATE DATABASE student DEFAULT CHARACTER SET = utf8mb4,一个建库语句。

新建表

然后我创建了一个名为student的表,Event_type也是Query,内容为use student; CREATE TABLE student (id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT),一个建表语句。

插入数据

然后我们执行INSERT语句给该表插入两行数据,再次查看binlog。

INSERT INTO `student` (`id`, `name`) VALUES (NULL, '张三');
INSERT INTO `student` (`id`, `name`) VALUES (NULL, '李四');

image

image-20210106123550397

可以看到每次INSERT都会开启一个事务,你可能会疑惑,我们只是简单的执行了INSERT语句,没有显示的开启事务。那为什么会有事务产生呢?

这是因为MySQL采用了自动提交(AUTOCOMMIT)的机制,我使用的InnoDB存储引擎,是支持事务的,所有的用户活动都发生在事务中。我们可以通过show variables like '%AUTOCOMMIT%';命令查看,如果结果是ON则代表是开启的。

1.3 复制的核心步骤

我们假设主库已经开启了binlog,并正常的记录binlog。

首先从库启动I/O线程,跟主库建立客户端连接。

主库启动binlog dump线程,读取主库上的binlog event发送给从库的I/O线程,I/O线程获取到binlog event之后将其写入到自己的Relay Log中。

然后从库启动SQL线程,将Relay中的数据进行重放,完成从库的数据更新。

总结来说,主库上只会有一个线程,而从库上则会有两个线程。

image

主从复制流程

1.4 Relay Log

relay log其实和binlog没有太大的区别,在MySQL 4.0 之前是没有Relay Log这部分的,整个过程中只有两个线程。但是这样也带来一个问题,那就是复制的过程需要同步的进行,很容易被影响,而且效率不高。例如主库必须要等待从库读取完了才能发送下一个binlog事件。这就有点类似于一个阻塞的信道和非阻塞的信道。

image

阻塞信道

阻塞信道就跟你在柜台一样,你要递归柜员一个东西,但是你和柜员之间没有可以放东西的地方,你就只能一直把文件拿着,直到柜员接手;而非阻塞信道就像你们之间有个地方可以放文件,你就直接放上去就好了,不用等柜员接手。

引入了Relay Log之后,让原本同步的获取事件、重放事件解耦了,两个步骤可以异步的进行,Relay Log充当了缓冲区的作用。Relay Log有一个relay-log.info的文件,用于记录当前复制的进度,下一个事件从什么Pos开始写入,该文件由SQL线程负责更新。

1.5 Relay Log核心参数

接下来让我们了解一下Relay Log的核心参数。

  • max_relay_log_size 中继日志的最大size,默认值0,如果为0就会取默认的size 1G,否则就为设置的值
  • relay_log 定义relay的名称,默认为主机名+relay-bin,例如像hostname-relay-bin
  • relay_log_basename 中继日志的全路径,即路径 + 文件名,例如/path/to/hostname-relay-bin,最大长度为256
  • relay_log_index 定义中继日志的索引文件的全路径,同样其最大的长度为256. 其默认值为hostname + relay-bin.index,例如/path/to/hostname-relay-bin.index
  • relay_log_info_file 定义relay-log.info文件的名称
  • relay_log_info_repository 存放relay log重放的数据的方式,可以设置为FILETABLE。FILE代表将中继日志重放的数据记录在relay-info.log中,TABLE则将其存放在slave_relay_log_info这张表里。
  • relay_log_purge 是否自动清空不需要的中继日志,默认值为ON
  • relay_log_recovery 当从库宕机后,如果relay log损坏了导致部分的中继日志没有进行同步,则自动放弃所有未进行重放的中继日志,并从主库重新获取,默认值为OFF
  • relay_log_space_limit 设置中继日志的最大值,防止写满磁盘。但是不建议设置这个值,建议还是给中继日志需要的空间,0就是不限制,0也是默认值
  • sync_relay_log 用于控制中继日志写入磁盘的变量,假设值为n,那么在中继日志每接受n次binlog事件之后就会调用fdatasync()函数将中继日志强制的刷入磁盘;相反,如果值为0,则写入OS的缓冲区内,由OS调度决定何时将中继日志刷入磁盘,这样一来如果在没有刷入之前报错了,那么中继日志就会丢失。默认值是10000,也就是每向中继日志中写入1w次binlog事件就将中继日志强制的刷入磁盘。
  • sync_relay_log_info 该参数的影响跟参数relay_log_info_repository有一定关系,同时也跟是否使用支持事务的存储引擎有关系。该值默认也是10000.
    • relay_log_info_repository为FILE,假设设置的值为N,那么每N次事务都会都会调用fdatasync()强制将relay-log.info刷入磁盘
    • relay_log_info_repository为TABLE,如果使用了支持事务的引擎,则该表每次事务结束都会被更新;如果没有使用事务引擎则会在写入N个binlog事件的时候更新该表。
    • relay_log_info_repository为FILE,MySQL不会调用fdatasync(),而是将刷入磁盘的调度交给OS;
    • relay_log_info_repository为TABLE,如果使用了支持事务的存储引擎,则每次事务的时候该表都会被更新;如果没有使用事务引擎,则永远不会被更新
    • sync_relay_log_info为0时
    • sync_relay_log_info大于0时
Comment