在主从复制实践一文中介绍了如何使用 docker 搭建一个主从复制的环境。那么,本篇文章就简单介绍一下 mysql 主从复制的原理。

1 主从复制

先看如下的原理图:

mysql8主从复制如何关闭 mysql8主从复制新特性_数据库


上面的图示大概分为如下三个步骤:

1、master 主库记录数据的更改记录(create,insert,update,delete,drop 操作)到 binlog (二进制日志)中。

2、主库有一个 log dump 线程来处理 slave 的 I/O 线程的请求,把 binlog 传给从库。主库会为每一个连接到它的从库开启一个 log dump 线程。也就是说从库是从主库来拉数据的。

3、salve 的 I/O 线程把读取到的 binlog 写入到 relay log(中继日志)中;

4、slave 的 SQL 线程会读取 relay log 文件中的日志,并解析成 sql 语句逐一执行,将更改的数据保存到从库中。

所以 mysql 主从复制的关键是主库的二进制日志文件

中继日志可以看做是一个缓冲,主节点不需要等从节点真正的将数据保存到数据库中就可以发送下一个事件了,剩下的由从节点的 SQL 线程慢慢往数据库写入数据就可以了。
中继日志与 binlog 日志的文件格式一样,可以用 mysqlbinlog 工具查看。

2 配置参数

在上一篇文章中没有介绍 master 和 slave 的配置文件中各个参数的作用,这小节就详细介绍一下。

2.1 master 的配置文件

我们将前一篇文章的主节点的配置文件复制过来:

[client]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4
[mysqld]
init_connect='set collation_connection=utf8mb4_unicode_ci; set names utf8mb4;'
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
skip-character-set-client-handshake
skip-name-resolve
server-id=1 #重要,主节点的 server-id 不能与从节点相同
log-bin=mysql-bin
binlog-ignore-db=mysql
binlog_cache_size=1M
binlog_format=mixed
expire_logs_days=7

这个配置文件只是列出了最基本的参数配置。
1、server-id=1 主服务器的 id 为 1,要保证在整个主从复制的集群中必须唯一。取值范围为1 到 4294967295,默认为 1 。如果设置 为 0 将拒绝副本连接到该节点。若从节点中该参数设置为 0 ,则不能连接到主节点

2、log-bin=mysql-bin,二进制日志的保存路径。可以是一个绝对路径。

3、binlog-ignore-db=mysql:此参数表示不记录指定的数据库的二进制日志。所以主从复制时,也就不会复制数据库名为 mysql 的数据。

4、binlog_format=mixed,二进制日志的记录格式。一般设置为 mixed 混合模式。

语句模式:Statement-base Replication (SBR)记录 sql 语句在bin log中,优点是只需要记录会修改数据的sql语句到binlog中,减少了binlog日志量,节约I/O,提高性能。缺点是在某些情况下,会导致主从节点中数据不一致。比如使用 sleep(),now() 函数给主库的 createDate = now() 设置为 2022-02-20 13:06:08 在从库中回放该语句时可能就变成了 2022-02-20 13:06:10 这样主从的数据就不一致了。更多情况可参考官网 https://dev.mysql.com/doc/refman/8.0/en/replication-sbr-rbr.html

行模式:Row-based Relication(RBR)是mysql master将SQL语句分解为基于行更改的语句并记录在bin log中,也就是只记录哪条数据被修改了,修改成什么样,比如 update user set status = 1 where id < 10,假如这条语句更改了 9 条数据,则二进制日志文件中就会记录这 9 行的数据变化 。优点是不会出现某些特定情况下的存储过程、或者函数、或者trigger的调用或者触发无法被正确复制的问题。缺点是会产生大量的日志,尤其是修改table的时候会让日志暴增,同时增加bin log同步时间。也不能通过bin log解析获取执行过的sql语句,只能看到发生的data变更。
混合模式:Mixed-format Replication(MBR),是以上两种模式的混合,对于一般的语句使用 STATEMENT 模式保存到binlog,对于 STATEMENT 模式无法复制的操作则使用 ROW 模式来保存,MySQL 会根据执行的 SQL语句选择日志保存方式。

5、expire_logs_days=7,二进制日志的保存天数,默认为 0 ,表示永久保存,本示例设置保存 7 天。

2.2 slave 的配置文件

[client]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4
[mysqld]
init_connect='set collation_connection=utf8mb4_unicode_ci; set names utf8mb4;'
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
skip-character-set-client-handshake
skip-name-resolve
server-id=2 # 与主节点不一样
log-bin=mysql-slave-bin
binlog-ignore-db=mysql
relay_log=mysql-relay-bin
binlog_cache_size=1M
binlog_format=mixed
expire_logs_days=7
slave_skip_errors=1062
read_only=1 #只读

1、relay_log=mysql-relay-bin 设置从节点的中继日志(relay log) 的路径。

mysql8主从复制如何关闭 mysql8主从复制新特性_database_02


一般情况下都设置一下该参数,若不设置的话默认按 host_name-relay-bin.nnnnnn 来设置。假如后面系统更改 host_name ,而已生成的中继日志的文件名字不会改变,则可能会出现 Failed to open the relay log and Could not find target log during relay log initialization的错误。

从上图中可以看到有两个中继日志文件:mysql-relay-bin.0000001 和 mysql-relay-bin.0000002 ,那什么时候会生成新的文件呢?

  • I/O 线程每次启动时会创建新的中继日志文件。
  • 当使用 FLUSH LOGS 或者 mysqladmin flush-logs 命令刷新日志时。
  • 当文件大小达到设置阈值时会重新创建文件。max_relay_log_size 大于 0 则以该值为准;若等于 0 则以 max_binlog_size 的值为准。

中继日志不需要显示的删除,当 SQL 线程执行完其中的所有事件后会自动删除。

2、slave_skip_errors=1062 在执行主从复制的过程中,如果出现错误,在默认情况下,服务器会停止复制进程,不再进行同步,等到用户自行来处理。如果不想让主从复制停止就可通过 slave_skip_errors 这个参数来跳过这些错误。1062 代表主键冲突的错误。假如由于误操作等原因使从数据库中添加了一个 id 为 1 的用户,然后在主库中也添加了这个用户,在进行主从复制时就会报 1062 错误。
关于这个参数的更多信息可参考这篇文章:

3、read_only=1 表示从库的普通用户只有只读权限,这样就能避免用户对从库的数据更改,导致与主库的数据不一致了。但是这个参数对具有超级管理员权限的用户不起作用,若要限制可设置 super_read_only=1。

2.3 其他参数

1、log_slave_updates=1 该参数默认是开启的,当我们需要实现级联复制时,如 A -> B -> C ,即 A 做为 B 的主库,B 又作为 C 的主库的时候就需要设置这个参数。默认情况下从库只会记录直接在该库上执行的SQL 语句,由 replication 机制的 SQL 线程读取 relay-log 而执行的 SQL 语句不会记录到 bin-log 日志中,那么就无法完成 B -> C 的复制。

2、replicate-do-db=test 需要复制的数据库名,如果复制多个数据库,重复设置这个选项即可:

replicate-do-db=test1
replicate-do-db=test2

3、replicate-ignore-db=mysql 需要忽略的数据库名,如果忽略多个数据库,重复设置这个选项即可。

replicate-ignore-db=mysql
replicate-ignore-db=test