☰
MySQL进阶篇之主从复制
2026/10/22 13:03:19 网站建设 项目流程

📚 本文收录于「bksczm」的系列专栏

LinuxC/C++
MySQL面试题
Redis

关于分布式和集群架构可以看Redis基础篇之初识分布式-CSDN博客,本篇博客以实现主从复制为主。

一、先搞懂原理:主从复制是怎么工作的

1.1 拓扑结构

我们要搭的是一主两从:

写请求(INSERT/UPDATE/DELETE/DDL) │ ▼ ┌────────────────┐ │ Master 主库 A │ │ 53306 │ └───────┬────────┘ 推送binlog│ 推送binlog ┌────────┴────────┐ ▼ ▼ ┌────────────────┐ ┌────────────────┐ │ Slave 从库 B │ │ Slave 从库 C │ │ 53307 │ │ 53308 │ └────────────────┘ └────────────────┘ 承担读请求 承担读请求

1.2 三个线程的完整工作流程

主从复制依赖binlog(二进制日志)和relay log(中继日志),涉及三个线程:

主库 Master 从库 Slave ┌──────────────┐ ┌──────────────────────┐ │ 客户端提交事务 │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ 写 binlog │ │ ① IO 线程 │ │ │ │ binlog事件 │ 连接主库、请求binlog │ │ ③ Binlog │◄───────────────│ 写入本地 relay log │ │ Dump 线程 │ 网络 │ │ │ │ 主动推送 │ ───────────────►│ ▼ │ └──────────────┘ │ ② SQL 线程 │ │ 读 relay log 重放 │ │ 变更本地数据 │ └──────────────────────┘
  1. 主库的 Binlog Dump 线程:从库连上来后,主库为每个从库创建一个 dump 线程,负责把 binlog 事件发送给从库;
  2. 从库的 IO 线程:连接主库、请求 binlog,收到后写入本地的relay log(中继日志);
  3. 从库的 SQL 线程:读取 relay log,按顺序重放(redo)成数据变更,使从库数据与主库一致。

所以主从延迟可以发生在两段:IO 线程拉取慢(网络)、SQL 线程重放慢(大事务/单线程)。

1.3 复制的三种同步方式

方式原理特点
异步复制(默认)主库本地提交完就返回客户端,不等从库性能最好,主库宕机可能丢失已提交事务
半同步复制 semi-sync主库至少等一个从库确认收到 binlog(写入 relay log)才返回折中,数据更安全;等待超时(默认 1 秒)后自动降级为异步
全同步等所有从库都执行完性能太差,MySQL 原生不直接提供,可用 MGR 等方案实现

1.4 关键前提:新从库要不要先导数据

  • 本示例是全新空主库:直接从SHOW MASTER STATUS看到的位点开始同步即可;
  • 如果主库已经有大量历史数据:MySQL 复制不会自动把历史数据全量搬给从库(这点和 Redis 主从会自动做 RDB 全量同步不同),必须先手动初始化:
    1. 用mysqldump --single-transaction --source-data=2或 XtraBackup 给主库做全量备份(--source-data会在文件头记录备份位点);
    2. 把备份导入空从库;
    3. 再从备份位点开始CHANGE REPLICATION SOURCE TO ...+START REPLICA。

二、Docker 基础:用容器模拟多台主机

2.1 准确理解容器、镜像、仓库

  • 镜像(Image):一个只读模板,把可执行文件和依赖(库、配置、运行环境)打包在一起;
  • 容器(Container):镜像运行起来的实例(镜像相当于"类",容器相当于"对象");
  • 仓库(Registry):存放镜像的地方,如 Docker Hub、阿里云镜像仓库;
  • 容器与宿主机共享同一个操作系统内核(不是各自装一个完整 OS),容器之间通过Namespace 隔离(进程、网络、文件系统等),通过Cgroup 限制(CPU、内存),所以容器比虚拟机轻量得多。

2.2 安装 Docker(Ubuntu)

# 1. 更新索引 sudo apt update # 2. 安装依赖和工具 sudo apt install -y ca-certificates curl gnupg lsb-release # 3. 创建密钥目录 sudo mkdir -p /etc/apt/keyrings # 4. 添加阿里云 Docker GPG 密钥 curl -fsSL [https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg](https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg) | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 5. 添加阿里云 Docker 软件源 echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ [https://mirrors.aliyun.com/docker-ce/linux/ubuntu](https://mirrors.aliyun.com/docker-ce/linux/ubuntu) $(lsb_release -cs) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 6. 安装 Docker sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-plugin # 7. 验证 sudo systemctl status docker sudo docker version

防火墙:教学环境为省事可sudo systemctl stop ufw;

生产环境不要直接关防火墙,而是按需放行端口。容器之间互联默认走 Docker 自己的网桥,与宿主机防火墙规则是两套东西。

2.3 配置镜像加速

编辑/etc/docker/daemon.json(国内拉 Docker Hub 常超时,配置加速器;部分免费加速器近年陆续失效,失效时更换可用地址即可):

{ "registry-mirrors": [ "[https://docker.m.daocloud.io](https://docker.m.daocloud.io)", "[https://dockerhub.timeweb.cloud](https://dockerhub.timeweb.cloud)", "[https://huecker.io](https://huecker.io)" ] }

加载并重启:

sudo systemctl daemon-reload sudo systemctl restart docker docker info # 查看 Registry Mirrors 是否生效

2.4 创建专用网络(容器互联用)

让三个容器通过容器名互相访问:

docker network create mysql-net

不用自建网络也可以,但那样容器之间只能用宿主机 IP + 映射端口互联,比较麻烦。


三、搭建主库(Master)

3.1 目录规划(挂载卷,数据持久化)

mkdir -p /czm/mysql/master/conf mkdir -p /czm/mysql/master/mysql

3.2 启动主库容器

docker run -d \ --name czm-mysql-master \ --network mysql-net \ -p 53306:3306 \ -v /czm/mysql/master/conf:/etc/mysql/conf.d \ -v /czm/mysql/master/mysql:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ --restart=always \ mysql:8.0

逐项解释原理:

参数作用
docker run由镜像创建并启动容器;本地没有该镜像时自动从 Registry 拉取
-d后台运行
--name容器名,后续docker exec/restart/stop都用它
--network mysql-net加入自建网络,之后从库可直接用容器名czm-mysql-master访问
-p 53306:3306端口映射:宿主机 53306 → 容器内 3306(docker-proxy/iptables 做 NAT),方便从宿主机外部用客户端连接
-v 主机目录:容器目录数据卷绑定挂载:容器内写/etc/mysql/conf.d、/var/lib/mysql实际落到宿主机,容器删除后数据和配置仍保留
-e MYSQL_ROOT_PASSWORD=123456注入环境变量;MySQL 官方镜像的入口脚本首次启动时读取它初始化 root 密码
--restart=alwaysDocker 重启后容器自动拉起
mysql:8.0镜像名:标签

官方镜像首次启动时发现/var/lib/mysql为空,会自动执行初始化(建系统库、设 root 密码);之后再启动发现目录非空则直接启动。

3.3 编写主库配置

在/czm/mysql/master/conf/my.cnf中写入:

[mysqld] # 集群内必须唯一 server-id=13803306 # 开启 binlog 及基本名 log-bin=binlog # 行格式,生产推荐 binlog_format=ROW # binlog 保留 10 天(默认 30 天 = 2592000 秒) binlog_expire_logs_seconds=864000 # 双1配置之一:每次事务提交都 fsync binlog sync-binlog=1 # 只复制/忽略指定库(默认复制全部) # binlog-do-db=db_name # binlog-ignore-db=db_name

3.4 重启并查看复制位点

docker restart czm-mysql-master

进入主库:

docker exec -it czm-mysql-master mysql -uroot -p123456

查看位点:

SHOW MASTER STATUS;
+---------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +---------------+----------+--------------+------------------+-------------------+ | binlog.000001 | 157 | | | | +---------------+----------+--------------+------------------+-------------------+

记住File 和 Position(你的实际值可能不同),从库要从这个位置开始同步。

3.5 创建复制账号(必须做)

从库需要一个账号连主库拉 binlog,在主库执行:

CREATE USER 'repl'@'%' IDENTIFIED BY '123456'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES;

'%'表示允许任意主机连接(Docker 内网环境)。

如果外部连接不上:

1.可能是操作不对

2.如果你的是云服务器,检查一下云服务器的防火墙对对应端口号是否开启!!!


四、搭建从库(Slave B)

4.1 目录与容器

mkdir -p /czm/mysql/slave1/conf mkdir -p /czm/mysql/slave1/mysql docker run -d \ --name czm-mysql-slave1 \ --network mysql-net \ -p 53307:3306 \ -v /czm/mysql/slave1/conf:/etc/mysql/conf.d \ -v /czm/mysql/slave1/mysql:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ --restart=always \ mysql:8.0

4.2 从库配置

在/czm/mysql/slave1/conf/my.cnf中写入:

[mysqld] # 必须与主库不同,集群内唯一 server-id=13803307 log-bin=binlog binlog_format=ROW binlog_expire_logs_seconds=864000 sync-binlog=1 ################## 从库专属配置 ################## # 中继日志基本名 relay-log=relay-bin # 启动时不自动开启复制,稍后手动 START REPLICA # (8.0.24 前用 skip-slave-start=ON) skip-replica-start=ON # 从库把自己重放的变更也写入自己的 binlog # 链式复制 A->B->C 时 B 必须开启(8.0.26 前为 log_slave_updates) # log-replica-updates=ON
docker restart czm-mysql-slave1

4.3 在从库建立复制关系

进入从库:

docker exec -it czm-mysql-slave1 mysql -uroot -p123456
CHANGE REPLICATION SOURCE TO SOURCE_HOST='czm-mysql-master', -- 自建网络下直接用容器名 SOURCE_PORT=3306, -- 容器内端口,不是宿主机映射的53306 SOURCE_USER='repl', SOURCE_PASSWORD='123456', SOURCE_LOG_FILE='binlog.000001', -- 来自主库 SHOW MASTER STATUS SOURCE_LOG_POS=157;

关于SOURCE_HOST:因为我们建了mysql-net自定义网络,直接填容器名;如果没建网络,就填宿主机 IP(或172.17.0.1Docker 默认网桥地址),端口填映射端口 53306。

4.4 启动复制

START REPLICA;

4.5 查看复制状态

SHOW REPLICA STATUS\G

重点看两个字段必须都是Yes:

  • Replica_IO_Running: Yes:IO 线程正常(网络连通、能拉到 binlog);
  • Replica_SQL_Running: Yes:SQL 线程正常(relay log 重放无报错)。

其他常用字段:

  • Last_IO_Error/Last_SQL_Error:出错时的具体原因(连不上、密码错、位点不对、主键冲突等);
  • Seconds_Behind_Master:从库落后主库的秒数(NULL 表示复制未运行)。

在主库/从库执行SHOW PROCESSLIST;可以看到:

  • 主库上有一个Binlog Dump线程(对应一个从库连接);
  • 从库上有replica_IO和replica_SQL两个线程。

4.6 第二个从库 C

完全重复从库 B 的步骤,注意三处区别:

  • 宿主机端口-p 53308:3306,容器名czm-mysql-slave2;
  • 目录换成/czm/mysql/slave2/...;
  • server-id 必须再换一个(如13803308),复制位点与 B 相同。

五、验证主从同步

在主库写数据:

CREATE DATABASE demo; USE demo; CREATE TABLE t(id INT PRIMARY KEY, name VARCHAR(20)); INSERT INTO t VALUES(1,'zhangsan'),(2,'lisi');

在两个从库查询:

SELECT * FROM demo.t;

能立刻看到数据,说明复制链路正常。再尝试在从库直接写(默认是允许的,除非加read_only=1),生产环境应设置从库只读:

# 从库 my.cnf 追加 read_only=ON super_read_only=ON

六、并行复制与主从延迟

6.1 并行回放

从库默认早期用单线程重放 relay log,主库高并发写入时容易跟不上。8.0 支持多线程并行复制:

SHOW VARIABLES LIKE 'replica_parallel_workers'; -- 8.0.27+ 默认 4
  • 设为0:单线程串行回放(最朴素,性能最低);
  • 设为 N(如 4/8):开启 N 个 worker 并行回放;
  • 配合replica_parallel_type=LOGICAL_CLOCK(基于主库组提交并行)或WRITESET(基于写集合,并行度更高)。

6.2 主从延迟的常见原因与对策

  • 原因:大事务、大批量 DML、DDL、从库硬件差、网络抖动、单线程回放;
  • 对策:
    1. 开启并行复制;
    2. 避免大事务、批量操作拆分;
    3. 对"写完立刻读"的强一致场景(如支付后跳订单页),读请求直接走主库,或用半同步 + 读一致性策略;
    4. 监控Seconds_Behind_Master。

七、读写分离与高可用

主从复制本身只是数据同步,读写分离和高可用还需要额外组件:

  1. 读写分离:写请求(INSERT/UPDATE/DELETE/DDL)全部路由到主库,读请求(SELECT)分发到多个从库;
    • 静态分离:应用代码里硬编码数据源;
    • 动态分离:通过中间件(ProxySQL、MaxScale、MyCat、ShardingSphere-JDBC)或应用层动态数据源自动路由;
  2. 负载均衡:读请求在多个从库间分发,摊平读压力;
  3. 故障转移(Failover):主库宕机时,需要把某个从库提升为新主(重新选主、其他从库改连新主),人工操作太慢,通常用MHA、Orchestrator、MySQL MGR(组复制)或云厂商的高可用方案自动完成;
  4. 数据一致性权衡:异步复制有延迟,从库可能读到旧数据,业务必须评估哪些读可以容忍延迟。

八、进阶:GTID 复制

传统复制要手动指定LOG_FILE + LOG_POS,主从切换、找位点很麻烦。MySQL 5.6+ 支持GTID(全局事务标识),每个事务有全局唯一 ID:

# 主从库都加 gtid_mode=ON enforce_gtid_consistency=ON

建立复制时无需手填位点,改为自动定位:

CHANGE REPLICATION SOURCE TO SOURCE_HOST='czm-mysql-master', SOURCE_PORT=3306, SOURCE_USER='repl', SOURCE_PASSWORD='123456', SOURCE_AUTO_POSITION=1; START REPLICA;

GTID 让从库自动判断从哪里续传,主从切换也更简单,是生产环境推荐方式。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询