MySQL 主从复制,核心是基于二进制日志(binlog)实现的数据同步:主库把数据变更记录到 binlog 里,从库拉取主库的 binlog,在本地重放,实现主从数据一致,主要用来做读写分离、数据备份、故障容灾。
修改 MySQL 的配置文件my.cnf(或my.ini),添加以下核心配置:
ini[mysqld]
# 唯一server_id,不能和从库重复
server_id = 1
# 开启二进制日志
log_bin = mysql-bin
# binlog格式,推荐ROW行级模式,数据一致性最好
binlog_format = ROW
# 要同步的业务数据库,不写默认同步所有库
binlog_do_db = 要同步的数据库名
# 过滤不同步的系统库
binlog_ignore_db = mysql
binlog_ignore_db = information_schema
binlog_ignore_db = performance_schema
# 主库关闭只读,可正常写入
read_only = 0
重启 MySQL 服务使配置生效:systemctl restart mysqld
在主库创建主从同步专用账号,给从库授权:
sql-- 创建同步账号,%代表从库任意IP都能访问,也可以指定从库IP提升安全性
CREATE USER 'repl'@'%' IDENTIFIED BY '你的同步密码';
-- 给账号授予主从复制权限
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%';
-- 刷新权限
FLUSH PRIVILEGES;
锁主库禁止写入,确保备份数据的一致性:
sqlFLUSH TABLES WITH READ LOCK;
查看主库的 binlog 状态,记录 File 和 Position 的值,后面从库配置必须用到:
sqlSHOW MASTER STATUS;
用 mysqldump 备份主库要同步的数据库,导出成 SQL 文件,拷贝到从库上。
解锁主库,恢复写入:
sqlUNLOCK TABLES;
修改 MySQL 的配置文件my.cnf,添加以下核心配置:
ini[mysqld]
# 唯一server_id,必须和主库不同
server_id = 2
# 开启中继日志
relay_log = mysql-relay-bin
# 开启只读,普通用户无法写入,超级用户除外
read_only = 1
# 要同步的数据库,和主库保持一致
replicate_do_db = 要同步的数据库名
# 过滤不同步的系统库
replicate_ignore_db = mysql
replicate_ignore_db = information_schema
replicate_ignore_db = performance_schema
重启 MySQL 服务使配置生效:systemctl restart mysqld
在从库导入主库的备份 SQL 文件,确保主从初始数据一致。
在从库配置主从同步,关联主库:
sqlCHANGE MASTER TO
MASTER_HOST='主库的IP地址',
MASTER_PORT=3306,
MASTER_USER='repl',
MASTER_PASSWORD='你设置的同步密码',
MASTER_LOG_FILE='主库SHOW MASTER STATUS里的File值',
MASTER_LOG_POS=主库SHOW MASTER STATUS里的Position值;
启动主从同步:
sqlSTART SLAVE;
查看主从同步状态:
sqlSHOW SLAVE STATUS\G
重点看Slave_IO_Running和Slave_SQL_Running两个字段,都是 Yes 就代表主从同步配置成功;Seconds_Behind_Master是 0,代表主从无延迟。
最常见的主从同步故障就是Slave_SQL_Running变成 No,核心原因是主从数据不一致、主键冲突、SQL 语句执行失败。我通常的处理方式是:先跳过错误的事务,再重新同步数据确保主从一致,同时配置主从同步状态的监控告警,提前发现同步异常。
js.
├── docker-compose.yml # 下面会写这个文件
├── master/
│ ├── data/ # 主库数据持久化目录(自动生成)
│ └── init/
│ └── init-master.sql # 主库初始化脚本
└── slave/
├── data/ # 从库数据持久化目录(自动生成)
└── init/
└── init-slave.sh # 从库自动配置脚本
jsmkdir -p master/data master/init slave/data slave/init
jsversion: '3.8'
services:
# 主库 (Master)
mysql-master:
image: mysql:8.0
container_name: mysql-master
environment:
MYSQL_ROOT_PASSWORD: root123 # 请自行修改密码
command: >
--server-id=1
--log-bin=mysql-bin
--binlog-format=ROW
--default-authentication-plugin=mysql_native_password
volumes:
- ./master/data:/var/lib/mysql # 数据持久化
- ./master/init:/docker-entrypoint-initdb.d # 初始化脚本挂载
ports:
- "3306:3306"
networks:
- mysql-repl-net
healthcheck:
# 健康检查:确保主库完全启动后再启动从库
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot123"]
interval: 5s
timeout: 10s
retries: 5
# 从库 (Slave)
mysql-slave:
image: mysql:8.0
container_name: mysql-slave
environment:
MYSQL_ROOT_PASSWORD: root123 # 请自行修改密码
command: >
--server-id=2
--relay-log=relay-bin
--read-only=1
--default-authentication-plugin=mysql_native_password
volumes:
- ./slave/data:/var/lib/mysql # 数据持久化
- ./slave/init:/docker-entrypoint-initdb.d # 初始化脚本挂载
ports:
- "3307:3306"
networks:
- mysql-repl-net
depends_on:
mysql-master:
condition: service_healthy # 等待主库健康检查通过
# 自定义网络,保证主从通信稳定
networks:
mysql-repl-net:
driver: bridge
在 master/init/ 目录下创建 init-master.sql
js-- 创建用于主从复制的专用用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'repl123';
-- 授予复制权限
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
在 slave/init/ 目录下创建 init-slave.sh: (这个脚本会自动去主库拿 Binlog 位置,无需手动操作)
js#!/bin/bash
set -e
# 1. 等待主库完全就绪
echo ">>>> 正在等待主库 MySQL 启动..."
until mysql -h mysql-master -uroot -proot123 -e "SELECT 1" &> /dev/null; do
sleep 2
done
# 2. 动态获取主库的 Binlog 文件名和位置
echo ">>>> 正在获取主库状态..."
MASTER_STATUS=$(mysql -h mysql-master -uroot -proot123 -e "SHOW MASTER STATUS\G")
MASTER_LOG_FILE=$(echo "$MASTER_STATUS" | grep "File:" | awk '{print $2}')
MASTER_LOG_POS=$(echo "$MASTER_STATUS" | grep "Position:" | awk '{print $2}')
echo ">>>> 检测到主库日志: File=$MASTER_LOG_FILE, Position=$MASTER_LOG_POS"
# 3. 配置从库连接
mysql -uroot -proot123 <<-EOSQL
STOP SLAVE;
CHANGE MASTER TO
MASTER_HOST='mysql-master',
MASTER_USER='repl',
MASTER_PASSWORD='repl123',
MASTER_LOG_FILE='$MASTER_LOG_FILE',
MASTER_LOG_POS=$MASTER_LOG_POS;
START SLAVE;
EOSQL
echo ">>>> 恭喜!主从复制配置自动完成!"
jsdocker-compose up -d
查看日志确认成功:
jsdocker-compose logs -f mysql-slave

验证同步状态:
进入从库数据库检查:
查看 Slave_IO_Running 和 Slave_SQL_Running 是否均为 Yes。
jsdocker exec -it mysql-slave mysql -uroot -proot123 -e "SHOW SLAVE STATUS\G"

在主库写入测试数据
进入主库:
jsdocker exec -it mysql-master mysql -uroot -proot123
在 MySQL 命令行中依次执行:
js-- 1. 创建测试库
CREATE DATABASE test_repl_db;
-- 2. 使用该库
USE test_repl_db;
-- 3. 创建一张表
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL
);
-- 4. 插入一条数据
INSERT INTO users (name) VALUES ('张三');
-- 5. 查看主库数据
SELECT * FROM users;
-- 6. 退出 MySQL
exit;
在从库验证数据同步
jsdocker exec -it mysql-slave mysql -uroot -proot123
请勿用root用户进行插入操作。
js-- 1. 查看是否有这个库(应该能看到 test_repl_db)
SHOW DATABASES;
-- 2. 使用该库
USE test_repl_db;
-- 3. 查看数据(应该能看到 '张三' 这条记录)
SELECT * FROM users;
第一步:在主库锁表并全量备份 这一步的目的是防止备份期间主库又有新数据写入,保证备份是一个一致性的快照。
js# 进入主库容器 docker exec -it mysql-master mysql -uroot -proot123
js-- 全局锁表(此时主库只能读,不能写)
FLUSH TABLES WITH READ LOCK;
-- 【关键】查看并记录主库当前的 Binlog 位置(一会儿要用)
SHOW MASTER STATUS;

注意: 执行完 FLUSH TABLES WITH READ LOCK; 后,千万不要退出这个 MySQL 终端,一退出锁就释放了。把这个窗口放一边,去开一个新的终端。 记录下 SHOW MASTER STATUS 的输出,例如:
js# 在主库执行全量备份,导出所有数据到本地的 backup.sql
docker exec mysql-master mysqldump -uroot -proot123 --single-transaction --all-databases --master-data=2 > backup.sql
js-- 解锁
UNLOCK TABLES;
-- 退出
exit;
js# 1. 停止从库的同步线程
docker exec -it mysql-slave mysql -uroot -proot123 -e "STOP SLAVE; RESET SLAVE ALL;"
# 2. 将备份文件拷贝到从库容器内
docker cp backup.sql mysql-slave:/tmp/
# 3. 导入数据(这步可能需要几秒钟,取决于数据量)
docker exec -i mysql-slave mysql -uroot -proot123 < backup.sql
这里需要用到第一步记录的 Binlog 位置(File 和 Position)。
jsdocker exec -it mysql-slave mysql -uroot -proot123
js-- 配置同步点(我已经帮你填好 File 和 Position 了)
CHANGE MASTER TO
MASTER_HOST='mysql-master',
MASTER_USER='repl',
MASTER_PASSWORD='repl123',
MASTER_LOG_FILE='mysql-bin.000004',
MASTER_LOG_POS=801;
-- 启动同步
START SLAVE;
-- 检查状态
SHOW SLAVE STATUS\G
看到 Slave_IO_Running: Yes 和 Slave_SQL_Running: Yes 就成功了!
本文作者:松轩(^U^)
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!