最近在折腾宿舍的服务器,想把 Ubuntu 上 Docker 里的 MySQL 主库,同步一份到我的飞牛 NAS 上做个备库。本以为是个常规操作,结果一路踩坑,从中午搞到晚上。这篇文章就把整个过程和所有踩过的坑记录下来,希望对你有帮助。
项目背景
主库:Ubuntu 服务器(IP: 192.168.3.10),MySQL 通过宝塔面板安装(宿主机版,非 Docker)。
备库:飞牛 NAS(IP: 192.168.3.9),MySQL 通过 fnOS 图形化 Docker 界面部署。
目标:实现 MySQL 主从复制,主库数据实时同步到备库。
最终成功的配置方案
一、主库(Ubuntu 宝塔 MySQL)配置
开启必要功能
在宝塔面板 -> 软件商店 -> MySQL -> 设置 -> 配置修改,在[mysqld]段下加入:[mysqld]
server-id=1
log-bin=mysql-bin
binlog_format=ROW
gtid_mode=ON
enforce_gtid_consistency=ON
保存后重启 MySQL。创建复制专用账号
登录主库 phpMyAdmin,执行 SQL:CREATE USER 'repl'@'%' IDENTIFIED BY '你的密码';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
⚠️ 坑点:MySQL 8.0 默认认证插件是caching_sha2_password,创建用户时不要加IDENTIFIED WITH mysql_native_password,否则会报错Plugin 'mysql_native_password' is not loaded。
二、备库(飞牛 NAS Docker MySQL)配置
准备目录和配置文件
/vol2/1000/docker/mysql/
├── conf/
│ └── replica.cnf ← 配置文件
└── data/ ← 数据目录replica.cnf 内容如下:
[mysqld]
server-id=2
relay-log=relay-bin
log-bin=mysql-bin
binlog_format=ROW
gtid_mode=ON
enforce_gtid_consistency=ON
read_only=ON
super_read_only=ON
⚠️ 坑点:fnOS 图形界面只能挂载目录对目录,不能挂载单个文件。所以要把 replica.cnf 放进 conf/ 目录,然后挂载 conf/ 目录到容器内的 /etc/mysql/conf.d/。MySQL 会自动加载该目录下所有 .cnf 文件。
启动容器
docker run -d \
--name mysql-slave \
--network bridge \
-p 3307:3306 \
-v /vol2/1000/docker/mysql/data:/var/lib/mysql \
-v /vol2/1000/docker/mysql/conf:/etc/mysql/conf.d \
-e MYSQL_ROOT_PASSWORD=你的密码 \
-e TZ=Asia/Shanghai \
mysql:8.0
数据同步与复制建立(标准流程)
锁定主库并记录位点
FLUSH TABLES WITH READ LOCK;
SHOW BINARY LOG STATUS;
-- 记下 File 和 Position,例如:mysql-bin.000001, 157
导出主库数据(排除 phpMyAdmin 系统表)
mysqldump -u root -p --all-databases --ignore-table=phpmyadmin.表名 > /root/full.sql
⚠️ 坑点:如果不排除 phpMyAdmin 的系统表,导入备库后会导致备库的 phpMyAdmin 配置出错,甚至打不开。
解锁主库
UNLOCK TABLES;
将数据导入备库
# 将 full.sql 传到飞牛 NAS
scp /root/full.sql root@192.168.3.9:/vol2/1000/docker/mysql/
# 导入备库
docker exec -i mysql-slave mysql -uroot -p'密码' < /vol2/1000/docker/mysql/full.sql
在备库建立复制关系(使用新语法)
STOP REPLICA;
RESET REPLICA ALL;
CHANGE REPLICATION SOURCE TO
SOURCE_HOST='192.168.3.10',
SOURCE_PORT=3306,
SOURCE_USER='repl',
SOURCE_PASSWORD='你的密码',
SOURCE_LOG_FILE='mysql-bin.000001',根据主库信息填写
SOURCE_LOG_POS=157;根据主库信息填写
START REPLICA;
SHOW REPLICA STATUS;
⚠️ 坑点:MySQL 8.4 及以上版本,CHANGE MASTER TO、START SLAVE、SHOW SLAVE STATUS 等旧语法已废弃,必须使用 CHANGE REPLICATION SOURCE TO、START REPLICA、SHOW REPLICA STATUS 等新语法。同样,SHOW MASTER STATUS 也要改为 SHOW BINARY LOG STATUS。
踩坑全记录(血泪史)
坑 1:备库 server-id 与主库冲突
现象:备库报错
Fatal error: source and replica have equal MySQL server ids原因:备库未配置
server-id,默认值为 1,与主库冲突。解决:在备库的
replica.cnf中设置server-id=2。
坑 2:fnOS 挂载配置文件方式错误
现象:容器启动报错
Can't read dir of '/etc/mysql/conf.d/'原因:在 fnOS 图形界面中将
replica.cnf文件直接挂载到了容器内的目录路径上,导致容器认为该路径是一个文件而非目录。解决:挂载
conf/目录到/etc/mysql/conf.d/。
坑 3:导入数据时包含了 phpMyAdmin 系统表
现象:备库的 phpMyAdmin 配置异常,无法正常使用。
原因:全量导出时未排除 phpMyAdmin 的系统表,导入后污染了备库的配置。
解决:导出时使用
--ignore-table=phpmyadmin.表名排除。
坑 4:导出数据后主库有新写入,导致主键冲突
现象:复制状态报错
Error_code: 1062(主键冲突)。原因:导出数据后,主库又有新的写入操作,这些操作的 binlog 被备库重放时,与已导入的历史数据发生主键冲突。
解决:导出前锁定主库(
FLUSH TABLES WITH READ LOCK),导出完成后记录位点并立即解锁,确保数据一致性。
坑 5:GTID 残留导致复制混乱
现象:
SHOW REPLICA STATUS中Retrieved_Gtid_Set出现多个不同的 UUID。原因:多次尝试建立复制关系导致备库积累了错误的 GTID 信息,与主库的 GTID 集合对不上。
解决:彻底重建备库容器,清空数据目录,重新导入数据和建立复制。
总结
折腾完这一趟,感觉比写一天代码还累。但收获也很大,对 MySQL 主从复制的理解深了不少。总结几点经验:
版本很重要:MySQL 8.4 语法变化很大,网上很多旧教程已经不适用了,一定要以官方文档为准。
环境差异要留意:宝塔面板的 MySQL 和纯 Docker 部署的 MySQL 配置路径完全不同,要先搞清楚自己的环境。
数据一致性是命门:导出数据前一定要锁定主库,记录准确的 binlog 位点,这是避免后续各种奇怪问题的关键。
备库配置要干净:备库的配置要独立,
server-id不能重复,GTID 状态要干净,最好从重建容器开始。
希望这篇文章能帮你少走弯路。如果你也在折腾类似的架构,欢迎留言交流!