📝 本文首发于 栏轩·阁
欢迎访问阅读原文,获取更好的阅读体验。
一、什么是主从复制?
MySQL 主从复制(Master-Slave Replication)是指将主库(Master)的数据库变更自动同步到一台或多台从库(Slave) 的机制。
为什么要做主从?
| 场景 | 说明 |
|---|---|
| 读写分离 | 主库处理写入(INSERT/UPDATE/DELETE),从库处理查询(SELECT),分摊压力 |
| 数据备份 | 从库可作为热备份,不影响主库性能 |
| 高可用 | 主库宕机时可手动 / 自动将从库提升为新主库 |
| 异构分析 | 从库可以配置不同的索引或引擎,专门跑报表 / OLAP |
二、复制原理(核心必读)
2.1 主从复制的工作流程
┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Master │ │ Slave │ │ Slave │
│ │ │ │ │ │
│ Client │ │ IO_Thread │ │ IO_Thread │
│ ↓ │ │ ↑ │ │ ↑ │
│ Write │ │ | │ │ | │
│ ↓ │ │ Relay_Log │ │ Relay_Log │
│ Binlog ◄─── │◄────────►│ ↑ │◄────────►│ ↑ │
│ │ 网络传输 │ SQL_Thread │ 网络传输 │ SQL_Thread │
│ │ │ ↓ │ │ ↓ │
│ │ │ Data │ │ Data │
└──────────────┘ └──────────────────┘ └──────────────┘
2.2 三个关键线程
| 线程 | 在哪运行 | 做什么 |
|---|---|---|
| Binlog Dump Thread | 主库 | 监听到 binlog 有变更 → 将变更推送给各个从库 |
| Slave IO Thread | 从库 | 连接主库,接收 binlog 事件 → 写入本地的 Relay Log(中继日志) |
| Slave SQL Thread | 从库 | 读取 Relay Log → 在本地重放 SQL,完成数据同步 |
2.3 两个关键日志
- Binary Log(binlog):主库记录所有数据变更的日志,是复制的源头
- Relay Log(中继日志):从库从主库拉取到的 binlog 副本,等待 SQL 线程回放
2.4 复制过程详解
1. 主库提交事务 → 写入 Binlog
2. 从库 IO Thread 连接到主库,请求 binlog
3. 主库 Binlog Dump Thread 将 binlog 事件发送给从库
4. 从库 IO Thread 接收 → 写入 Relay Log
5. 从库 SQL Thread 读取 Relay Log → 在本地执行 SQL
6. 数据同步完成(可能会有延迟)
三、复制类型
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 异步复制(Async) | 主库不等待从库确认,性能最好,可能有数据丢失 | 默认模式,对性能要求高的场景 |
| 半同步复制(Semi-Sync) | 主库等待至少一个从库写入 Relay Log 后才返回成功 | 数据安全要求较高的业务 |
| 全同步复制(Group Replication) | 主库等待所有从库提交后才返回,强一致性 | 金融场景,但性能下降明显 |
本教程默认搭建的是异步复制。8.0 开始默认使用异步复制,可以手动开启半同步。
四、Binlog 格式
| 格式 | 说明 | 优缺点 |
|---|---|---|
| STATEMENT | 记录 SQL 语句本身 | 日志小,但某些函数(NOW(), UUID())导致主从不一致 |
| ROW(推荐) | 记录每一行数据的变更 | 一致性好,日志较大;MySQL 5.7+ 默认 |
| MIXED | 混合模式:SQL 语句安全的用 STATEMENT,不安全的自动切 ROW | 兼顾两者 |
生产中推荐使用 ROW 格式,一致性最高。
五、环境准备
- Docker & Docker Compose
- MySQL 8.0 镜像
- Linux / macOS / Windows (WSL2) 均可
六、搭建步骤
6.1 创建目录结构
bash
mkdir -p mysql-cluster/{master,slave1,slave2}/{conf,logs,data} && \
touch mysql-cluster/docker-compose.yml && \
touch mysql-cluster/{master,slave1,slave2}/conf/my.cnf
目录说明:
| 路径 | 用途 |
|---|---|
./master/conf/ |
主库 MySQL 配置文件 |
./master/logs/ |
主库日志(含 binlog、error log) |
./master/data/ |
主库数据文件(ibd、ibdata 等) |
./slave1/conf/ |
从库 1 配置文件 |
./slave1/logs/ |
从库 1 日志 |
./slave1/data/ |
从库 1 数据 |
6.2 修改配置文件权限
Docker 挂载配置文件时,MySQL 容器内的 mysqld 对配置的权限有要求。如果权限太高(如 777),容器内部会忽略配置:
bash
chmod 644 ./mysql-cluster/slave1/conf/my.cnf
chmod 644 ./mysql-cluster/slave2/conf/my.cnf
chmod 644 ./mysql-cluster/master/conf/my.cnf
644表示所有者可读写,其他用户只读,这是配置文件的安全推荐权限。
6.3 主库配置文件
master/conf/my.cnf
ini
[mysqld]
# 基础设置
server-id = 1 # 集群内唯一,不能重复
log-bin = mysql-bin # 开启 binlog,文件名前缀
binlog-format = ROW # 使用 ROW 格式(推荐)
expire-logs-days = 7 # binlog 保留 7 天自动清理
# 需要同步的数据库(可选)
# binlog-do-db = testdb # 只记录这个库的变更
# 不需要同步的数据库(可选)
# binlog-ignore-db = mysql # 忽略系统库
# 其他推荐配置
binlog_rows_query_log_events = ON # ROW 模式下也记录原始 SQL,便于排查
max_binlog_size = 100M # 单个 binlog 文件大小上限
innodb-file-per-table = 1 # 每个表独立表空间
关键参数详解:
server-id:集群中每个节点的唯一标识,不能重复log-bin:开启 binlog。主库必须开启,从库可选(开启后可作为级联复制的中继)binlog-format = ROW:记录行变更而非 SQL 语句,确保数据一致性expire-logs-days:自动清理过期 binlog,防止磁盘写满binlog_rows_query_log_events:ROW 模式下记录原始 SQL 帮助定位问题
6.4 从库配置文件
slave1/conf/my.cnf
ini
[mysqld]
server-id = 2 # 不同从库必须不同
relay-log = relay-bin # 中继日志前缀
log-bin = mysql-bin # 从库也开启 binlog(可选,用于级联复制或从库作为新主库)
binlog-format = ROW
read-only = 1 # 从库只读(超级用户除外)
relay-log-purge = 1 # 自动清理已应用的中继日志
skip-slave-start = 0 # 启动时自动开始同步(8.0 默认行为)
slave2/conf/my.cnf
ini
[mysqld]
server-id = 3 # 不同从库必须不同
relay-log = relay-bin
log-bin = mysql-bin
binlog-format = ROW
read-only = 1
relay-log-purge = 1
关键参数详解:
server-id = 2/3:每个从库必须不同,且与主库不同relay-log:从库专有的中继日志,存储从主库拉取到的 binlogread-only = 1:防止数据误写入从库导致主从不一致(注意super用户不受限)relay-log-purge = 1:SQL 线程应用完后自动删除中继日志,节省空间
6.5 Docker Compose 配置
docker-compose.yml
yaml
version: '3.8'
services:
mysql-master:
image: mysql:8.0
container_name: mysql-master
restart: always
environment:
MYSQL_ROOT_PASSWORD: root123
# 下面两个环境变量不是 MySQL 官方支持的,这里只是占位说明
# 真正的复制用户我们稍后手动创建
ports:
- "3306:3306" # 宿主机端口:容器端口
volumes:
- ./master/conf:/etc/mysql/conf.d # 配置文件
- ./master/logs:/var/log/mysql # 日志
- ./master/data:/var/lib/mysql # 数据
networks:
- mysql-cluster-net
mysql-slave1:
image: mysql:8.0
container_name: mysql-slave1
restart: always
environment:
MYSQL_ROOT_PASSWORD: root123
ports:
- "3307:3306"
volumes:
- ./slave1/conf:/etc/mysql/conf.d
- ./slave1/logs:/var/log/mysql
- ./slave1/data:/var/lib/mysql
depends_on:
- mysql-master # 确保主库先启动
networks:
- mysql-cluster-net
mysql-slave2:
image: mysql:8.0
container_name: mysql-slave2
restart: always
environment:
MYSQL_ROOT_PASSWORD: root123
ports:
- "3308:3306"
volumes:
- ./slave2/conf:/etc/mysql/conf.d
- ./slave2/logs:/var/log/mysql
- ./slave2/data:/var/lib/mysql
depends_on:
- mysql-master
networks:
- mysql-cluster-net
networks:
mysql-cluster-net:
driver: bridge # 容器间可通过容器名互通
端口映射规则:
| 容器 | 宿主机端口 | 容器端口 | 用途 |
|---|---|---|---|
| mysql-master | 3306 | 3306 | 本机连接主库 |
| mysql-slave1 | 3307 | 3306 | 本机连接从库1 |
| mysql-slave2 | 3308 | 3306 | 本机连接从库2 |
6.6 启动容器
bash
cd mysql-cluster
docker-compose up -d
检查状态:
bash
docker-compose ps
确认三个容器都是 Up 状态。
6.7 检查容器间连通性
bash
# 从 slave1 ping master(验证 Docker 内 DNS 解析)
docker exec mysql-slave1 ping mysql-master -c 2
# 如果容器内没有 ping,也可以测试端口
docker exec mysql-slave1 mysql -h mysql-master -u root -proot123 -e "SELECT VERSION();"
Docker Compose 创建的容器在同一网络内可以通过 容器名 互相通信,因此从库连接主库用
MASTER_HOST='mysql-master'即可。
七、配置主从同步
7.1 给每个 MySQL 创建 test 数据库(可选,用于测试)
分别在三个容器中执行:
sql
CREATE DATABASE test;
bash
# 快速在三个容器执行
for name in mysql-master mysql-slave1 mysql-slave2; do
docker exec $name mysql -uroot -proot123 -e "CREATE DATABASE IF NOT EXISTS test;"
done
7.2 在主库创建复制专用用户
bash
docker exec -it mysql-master mysql -uroot -proot123
sql
-- 创建复制用户,允许从任意 IP 连接(%)
CREATE USER 'repl'@'%' IDENTIFIED BY 'repl123';
-- 授予复制所需的权限(只需要 REPLICATION SLAVE)
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
-- 刷新权限
FLUSH PRIVILEGES;
为什么权限是
REPLICATION SLAVE而非ALL?最小权限原则:复制进程只需要读取主库的 binlog 即可,不需要 INSERT / DELETE 等权限。
这里用
*.*表示可以复制所有库,也可以指定test.*限制只复制某个库。
7.3 获取主库的 Binlog 坐标
sql
SHOW MASTER STATUS;
输出示例:
| File | Position |
|---|---|
| mysql-bin.000001 | 157 |
重要 :记住
File和Position,这是从库开始复制的起点坐标。如果在创建复制用户后又有写入操作,Position 会变化,需要重新查询。
7.4 配置从库(两个从库操作相同)
bash
docker exec -it mysql-slave1 mysql -uroot -proot123
sql
-- 停止已有的同步线程(如果有的话)
STOP SLAVE;
-- 重置从库状态
RESET SLAVE;
-- 配置主库信息
CHANGE MASTER TO
MASTER_HOST='mysql-master', -- Docker 网络内容器名
MASTER_USER='repl',
MASTER_PASSWORD='repl123',
MASTER_PORT=3306,
MASTER_LOG_FILE='mysql-bin.000001', -- 替换成第 7.3 步记下的 File
MASTER_LOG_POS=157, -- 替换成第 7.3 步记下的 Position
GET_SOURCE_PUBLIC_KEY=1; -- MySQL 8.0 需要获取公钥(caching_sha2_password)
-- 启动同步
START SLAVE;
-- 查看同步状态
SHOW SLAVE STATUS\G
对 slave2 执行相同操作(进入 slave2 容器执行相同的 SQL,注意 server-id 已经在配置文件中设置好了)。
GET_SOURCE_PUBLIC_KEY=1说明:MySQL 8.0 默认使用
caching_sha2_password认证插件,从库连接主库时需要获取公钥进行加密通信(密码传输加密),否则会报错:
Authentication plugin 'caching_sha2_password' reported error
7.5 同步状态检查
SHOW SLAVE STATUS\G 的输出很关键
核心指标汇总:
| 指标 | 正常值 | 异常处理 |
|---|---|---|
Slave_IO_Running |
Yes | 检查网络、用户权限、主库 binlog |
Slave_SQL_Running |
Yes | 检查是否有 SQL 冲突、表结构不一致 |
Seconds_Behind_Master |
0 | 数值越大表示延迟越大 |
Last_IO_Error / Last_SQL_Error |
空 | 非空时是排障的第一线索 |
八、验证主从同步
8.1 基本验证
在主库创建数据库、表并插入数据:
sql
-- 在主库执行
CREATE DATABASE test_repl;
USE test_repl;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('Charlie');
在从库查询:
sql
-- 在从库执行
USE test_repl;
SELECT * FROM users;
如果能看到三行数据,说明主从同步正常。
8.2 验证从库只读
sql
-- 在从库尝试写入
USE test_repl;
INSERT INTO users (name) VALUES ('Eve');
期望结果:
ERROR 1290 (HY000): The MySQL server is running with the --read-only option
这就是
read-only=1的作用。但注意root等SUPER用户仍然可以写入,普通应用用户会被阻止。
8.3 验证主库写入 → 从库实时同步
sql
-- 主库执行
INSERT INTO users (name) VALUES ('Dave'), ('Eve');
SELECT * FROM users;
然后即时去从库查询,数据应该已同步。
九、总结
架构总览
┌─────────────┐
│ Client │
└──────┬──────┘
│ 读/写
▼
┌────────┐ ┌──────────────┐
│ Master │──────►│ Slave 1 │
│ 3306 │ │ (read-only) │
│ R/W │ │ 3307 │
└────────┘ └──────────────┘
│
│ ┌──────────────┐
└──────────►│ Slave 2 │
│ (read-only) │
│ 3308 │
└──────────────┘
进阶说明
- 主从复制 = 主库写 binlog → 从库拉取到 relay log → 从库回放,三个线程协同
- ROW 格式 + GTID 是生产环境的推荐配置
- 从库必须
read-only=1,防止数据不一致 - 监控
Slave_IO_Running、Slave_SQL_Running、Seconds_Behind_Master三大指标 - 半同步复制 可以在性能和一致性之间取得平衡
- Docker 部署时注意 配置文件权限、网络模式、认证插件(8.0 默认
caching_sha2_password)