主从原理

核心作用

  • 数据热备份、故障切换;

  • 读写分离:主库写、从库读、分担查询压力;

  • 数据分发、异地多活、数据审计;

整体架构与三大线程

主从复制依赖二进制日志 binlog传输数据,一共 3 个核心线程:

  1. 主库:Dump 线程(Binlog Dump Thread)

    1. 当从库 IO 线程连接主库后,主库创建 Dump 线程;

    2. 读取本地 binlog 文件,将新增日志实时推送给从库 IO 线程;

    3. 多个从库连接主库,主库会生成多条独立 Dump 线程。

  2. 从库:IO 线程(Slave_IO_Running)

    1. 连上主库,请求 binlog 数据;

    2. 接收主库发来的日志,写入本地的中继日志(Relay Log)

    3. 记录当前已接收到哪个位置,断线后能接着续传。

  3. 从库:SQL 线程(Slave_SQL_Running)

    1. 读取中继日志里的 binlog 事件;

    2. 在从库上重放执行这些事件(重新执行 SQL 或行变更);

    3. 记录已执行到的位置,执行完的中继日志可以清理。

补充总结:

  1. 两个关键文件(在实际运维中常被问及):

    • master.info:记录 IO 线程拉取到的 binlog 位置(主库的哪条日志);

    • relay-log.info:记录 SQL 线程回放到的位置(中继日志的哪条)。

  2. 复制方式的定性

    • 默认是异步复制——主库写完 binlog 就返回成功,不管从库有没有收到;

    • 如需强一致性,可改用半同步复制(至少一个从库收到日志后才返回)。

常见架构

  1. 一主一从:最简单架构,主库写,从库读+备份;

  2. 一主多从:主库写,多从库分担读压力(适合读密集场景);

  3. 级联复制(主→从→从):主库只同步给一个从库(二级主库),再由二级主库同步给其他从库,减轻主库的 dump 线程压力;

  4. 双主复制:两个主库互为主从,均可写操作(需避免冲突,适合写分散场景)。

复制类型

1. 异步复制(默认)

  • 流程:主库执行完写操作 → 写入 binlog → 立即向客户端返回成功(不等待从库同步);

  • 优点:性能好(主库无等待);

  • 缺点:主库崩溃时,可能有数据未同步到从库,导致数据丢失。

2. 半同步复制(Semi-Sync)

  • 流程:主库执行完写操作 → 写入 binlog → 等待至少一个从库收到 binlog 并写入中继日志 → 向客户端返回成功;

  • 优点:数据安全性高于异步(至少一个从库有备份);

  • 缺点:性能略低于异步(主库需等待从库确认)。

3. 全同步复制(Full-Sync)

  • 流程:主库执行完写操作 → 等待所有从库执行完 binlog 事件并返回成功 → 向客户端返回成功;

  • 优点:数据零丢失(主从数据完全一致);

  • 缺点:性能差(主库等待时间长,适合对数据一致性要求极高的场景)。

主从配置设置

主库配置

 # 配置 my.cnf
 vim /etc/my.cnf
 ​
 # 配置文件内容(在mysqld下面添加)
 [mysqld]
 # 主从复制-主机配置
 # 主服务器唯一ID
 server-id=1
 # 启用二进制日志
 log-bin=mysql-bin
 # 在数据目录创建日志等价于 log-bin=/usr/local/mysql/data/mysql-bin
 # 设置不要复制的数据库(可设置多个)
 binlog-ignore-db=sys
 binlog-ignore-db=mysql
 binlog-ignore-db=information_schema
 binlog-ignore-db=performance_schema
 # 设置需要复制的数据库(可设置多个)
 # binlog-do-db=test
 # 设置logbin格式
 binlog_format=STATEMENT

从库配置

 # 配置 my.cnf
 vim /etc/my.cnf
 ​
 # 配置文件内容(在mysqld下面添加)
 [mysqld]
 # 在之前配置下方编写
 # 主从复制-从机配置
 # 从服务器唯一ID
 server-id=2
 # 启用中继日志
 relay-log=mysql-relay
 # 在数据目录创建日志等价于 relay-log= /usr/local/mysql/data/mysql-relay

重启服务

 # 重启两台服务器上的mysql
 systemctl restart mysqld

关闭防火墙或开放3306端口

 # 关闭主从数据库服务器防火墙或开放3306端口
 ## 查看防火墙状态
 systemctl status firewalld
 ​
 ## 关闭防火墙
 systemctl stop firewalld
 ​
 # 永久开放3306端口
 firewall-cmd --permanent --add-port=3306/tcp
 ​
 # 重新加载防火墙规则
 firewall-cmd --reload
 ​
 # 验证是否生效
 firewall-cmd --list-ports

主数据库创建用户slave并授权

 # 主数据库创建用户slave并授权
 ## 在主数据库端(192.168.7.21)
 ## 登录mysql -uroot -p
 ## 创建用户
 create user 'slave'@'%' identified with mysql_native_password by '123456';
 ​
 ## 授权
 grant replication slave on *.* to 'slave'@'%';
 ​
 ## 刷新权限
 flush privileges;

从数据库端验证主数据库slave用户是否可用

 # 从数据库端验证主数据库slave用户是否可用
 ## 在从数据库端(192.168.7.22)
 ## 验证主数据库slave用户是否可用
 mysql -uslave -p -h192.168.7.21 -P3306
 ​
 # 成功提示
 会进入主数据库

配置主从节点信息

 # 配置主从节点信息
 ## 在主数据库端(192.168.7.21)
 ## 查询服务ID及Master状态
 ## 登录
 mysql -uroot -p
 ​
 # 查询server_id是否可配置文件中一致
 show variables like 'server_id';
 ​
 # 若不一致,可设置临时ID(重启失效)
 set global server_id = 100;
 ​
 # 查询Master状态,并记录 File(对应下一步中的master_log_file)
 # Position (对应下一步中的master_log_pos)的值
 show master status;
 ​
 # 注意:执行完此步骤后退出主数据库
 # 防止再次操作导致 File 和 Position 的值发生变化

在从数据库端设置同步

 # 在从数据库端设置同步
 # 在从数据库端(192.168.7.22)
 # 登录
 mysql -uroot -p
 ​
 # 查询server_id是否可配置文件中一致
 show variables like 'server_id';
 ​
 # 若不一致,可设置临时ID(重启失效)
 set global server_id = 101;
 ​
 # 设置主数据库参数(用上一步创建的slave用户及密码)
 change master to 
 master_host='192.168.7.21',
 master_port=3306,
 master_user='slave',
 master_password='123456',
 master_log_file='mysql-bin.000001',         -- 主服务器提供
 master_log_pos=828;                         -- 主服务器提供
 ​
 # 开始同步
 start slave;
 ​
 # 查询Slave状态
 show slave status\G;
 ​
 # 查看是否配置成功
 # 查看参数 Slave_IO_Running 和 Slave_SQL_Running 是否都为yes,则证明配置成功。若为no,则需要查看对应的 Last_IO_Error 或 Last_SQL_Error 的异常值
 ​
 # 若出现错误,则停止同步,重置后再次启动
 stop slave;
 reset slave;
 start slave;
 ​
 # 可以在从服务器my.cnf中添加
 slave_skip_errors = ALL  # 跳过错误,继续同步
 ​
 # 重启MySQL服务
 systemctl restart mysqld

测试是否成功

测试主从复制,主服务上执行

 # 在主数据库端(192.168.7.21)
 mysql -uroot -p
 # 创建test库,t1表,添加测试数据
 create database test;
 use test;
 create table t1(id int,name varchar(30));
 insert into t1(id,name) values(1,"aaa");

测试主从复制,从服务器上执行

 # 在从数据库端(192.168.7.22)
 mysql -uroot -p
 # 查看是否同步数据
 show databases;
 use test;
 show tables;
 select * from t1;

GTID 方案

GTID 的全称是 Global Transaction Identifier,全局事务 ID,当一个事务提交时,就会生成一个 GTID,相当于事务的唯一标识。

GTID 长这样:

 c5d74746-d7ec-11ec-bf8f-0242ac110002:1

结构:

 GTID=server_uuid:gno

如何配置

 # 修改主库和从库的配置文件:
 vim /etc/my.cnf
 ​
 # 添加配置文件
 [mysqld]
 ## 在配置文件中添加GTID配置
 gtid_mode=on
 enforce_gtid_consistency=on
 ​
 解释:
 ​
 gtid_mode=on                         #GTID 是一个全局唯一的事务标识符,用于标识数据库集群中的事务。启用 GTID 后,每个事务都会被分配一个唯一的 GTID。
 enforce_gtid_consistency=on         #当启用 GTID 后,该选项用于强制要求从库只能复制具有与主库一致的 GTID 链的事务,以确保数据一致性。
 ​
 # 从库配置同步的参数:
 ## 格式
 CHANGE MASTER TO
 MASTER_HOST=$host_name,
 MASTER_PORT=$port,
 MASTER_USER=$user_name,
 MASTER_PASSWORD=$password,
 master_auto_position=1;
 ​
 ## 例如:
 change master to
 master_host='192.168.8.100',
 master_port=3306,
 master_user='slave',
 master_password='root123',
 master_auto_position=1;

GTID和传统模式切换方法

在线将传统复制切换到GTID复制

 ## 在主从库执行
 set global enforce_gtid_consistency=WARN;
 set global enforce_gtid_consistency=on;
 set @@GLOBAL.GTID_MODE = OFF_PERMISSIVE;
 ​
 ## 先在从库上执行
 set @@GLOBAL.GTID_MODE = ON_PERMISSIVE;
 ​
 ## 然后在主库执行
 set @@GLOBAL.GTID_MODE = ON_PERMISSIVE;
 ​
 # 查看是否为0,等于0是表示所有连接都转为gtid复制
 show status like 'ONGOING_ANONYMOUS_TRANSACTION_COUNT';
 ​
 # 主从库执行
 set global gtid_mode=on;
 ​
 # 从库执行
 stop slave;
 change master to master_auto_position=1;
 start slave;
 ​
 ## 永久生效,记得更改配置文件

在线将GTID的环境切换到非GTID

 # 从库执行
 stop slave;
 change master to master_auto_position=0;
 start slave;
 ​
 # 主从库执行
 set @@GLOBAL.GTID_MODE = ON_PERMISSIVE
 ​
 # 主库执行
 set @@GLOBAL.GTID_MODE = OFF_PERMISSIVE
 # 这个时候已经从gtid转到传统复制了,不过还是中间状态
 ​
 # 在从库执行
 set @@GLOBAL.GTID_MODE = OFF_PERMISSIVE
 ​
 # 主从库执行
 select @@GLOBAL.GTID_OWNED;
 # 是不是为空
 ​
 # 主库执行
 set global gtid_mode=0;
 ​
 # 主库执行
 set global enforce_gtid_consistency=off;
 ​
 ## 永久生效,记得配置文件修改