news 2026/9/1 6:34:35

基于Docker Compose的MySQL一主二从复制配置实战与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Docker Compose的MySQL一主二从复制配置实战与避坑指南

简介:面向需要快速搭建MySQL主从复制环境的Docker用户,这里提供了一套基于Docker Compose的一主二从配置方案,解决开发与测试环境中数据库读写分离、数据冗余和负载均衡的部署难题。包内以docker-compose.yml为中心,配套primary、replica1、replica2三个实例的initdb与conf.d目录,包含.cnf配置文件、.sh初始化脚本和.sql建表脚本共8个文件,压缩包小巧仅5KB,目录层级直观,适合直接套用或按需修改。配置逻辑完整覆盖主库server-id与log-bin开启、从库read-only限制、主从复制账号及权限设置,并通过depends_on控制容器启动顺序,同时给出健康检查与等待MySQL就绪的注意点,可避免常见的从库连接失败问题。已有39人学习下载,适合具备一定Docker基础、希望规避手工配置多容器复杂度的运维和开发人员参考,能显著缩短一主二从环境的搭建与排查时间。 干过几年数据库维护的都知道,单库跑业务就像走钢丝,读请求一多主库CPU直接拉满,慢查询能把整个业务拖垮。这阵子我基于docker-compose搭了一套MySQL一主二从,用来做读写分离和容灾演练,整个过程踩了一些坑,这篇文章把配置从头到尾讲清楚。内容包含完整的docker-compose文件、主从配置文件、复制账号初始化、复制状态验证,以及我在实际部署中遇到的高频问题。适合已经会MySQL基础操作、想快速复现一套主从复制环境的开发者,也适合准备把数据库高可用方案落地的运维同学参考。

1. 主从复制方案整体设计

1.1 为什么选MySQL原生binlog复制

MySQL主从复制最经典的实现方式,就是基于binlog的异步复制。主库把变更写入二进制日志,从库上的I/O线程拉取并写到本地relay log,SQL线程再回放relay log。整个链路很简单,数据最终一致,性能开销也不大。

有人可能会问,为什么不用双主、组复制或者半同步?我的判断很直接:一主二从的目标是解决读压力、隔离备份,不需要自动故障转移。原生异步复制足够达到目的,而且排错路径清晰。用组复制虽然能提供高可用,但架构复杂度明显上升,测试环境没必要一上来就堆这么多东西。

我在配置里明确开启了GTID模式。GTID就是每个事务的全局唯一标识,相比传统基于binlog文件名+偏移量的定位方式,GTID让从库连接主库时的位置判断变得自动且可靠。尤其是在容器重启、从库重搭的场合,GTID能省掉一大半手工对齐位置的心智负担。

1.2 为什么用docker-compose而不是直接装多个MySQL实例

以前我在服务器上搭主从,习惯直接装好几个MySQL实例,或者用mysqld_multi管理。结果有两个痛点:一是配置分散,每个实例的my.cnf容易改乱;二是环境不可复现,换台机器就得重新搞一遍。

docker-compose的好处是“基础设施即代码”。一个docker-compose.yml把三个MySQL服务的镜像、端口、数据卷、网络、健康检查全部定义清楚,一条命令拉起整套环境,删了重建也无所谓,数据放在宿主机目录,不会轻易丢。

当然我这里要说明白:生产环境如果跑的是高并发业务,我不建议用docker跑数据库,毕竟容器网络和磁盘IO多多少少会有损耗。但这次搭建的目的是测试、学习和做读写分离演练,docker-compose就是性价比最高的选择。真到生产环境,思路一致,只是部署形态需要换成物理机或云数据库。

1.3 拓扑与端口规划

我规划的拓扑很简单:一个主库master,两个从库slave1、slave2。三个容器跑在同一个自定义bridge网络内,相互之间用服务名通信,这样从库在容器内访问主库时永远走3306端口,不需要关心宿主机端口映射。

角色容器名宿主机端口容器内端口server-id
主库mysql-master330633061
从库1mysql-slave1330733062
从库2mysql-slave2330833063

数据卷我这样规划:主库数据放在./data/master,从库1放在./data/slave1,从库2放在./data/slave2。外部挂载的好处是容器升级、重搭不会丢数据。这里有一个容易忽略的细节:三个容器的server-id必须不同,否则从库连接主库后会出现复制ID冲突,表现就是Slave_SQL_Running异常。

2. 环境准备与目录规划

2.1 依赖确认

在动手之前,先把环境确认好,省得后面各种灵异问题。

需要安装的东西是Docker和Docker Compose插件。现在主流的docker compose命令是docker compose,老项目里还能看到docker-compose这种写法,底层逻辑一样。检查命令如下:

docker --version docker compose version

如果没有Docker,不同发行版安装方式不太一样,这里不展开。我的建议是直接用官方源安装最新稳定版,别用系统自带的旧版本。Compose版本太旧会导致部分语法解析失败,比如healthcheck字段、restart: unless-stopped这类配置在旧版本上不识别。

2.2 镜像选择

镜像我用的是mysql:8.0。选8.0的核心原因是MySQL 8.0已经是当前主流版本,无论是安全特性还是性能都比5.7强很多。需要注意一点:8.0默认的认证插件是caching_sha2_password,这对我们后面创建复制账号有影响,我会在第五节专门讲。

如果不熟悉8.0,想先拿5.7练习,逻辑也完全一致,只是部分参数默认值不同。文中的命令和配置在5.7上基本也能跑,只是复制账号的认证插件问题在5.7上不那么突出。

2.3 目录结构

项目目录我建成了这样:

mysql-cluster/ ├── docker-compose.yml ├── master/ │ ├── conf/my.cnf │ └── data/ ├── slave1/ │ ├── conf/my.cnf │ └── data/ └── slave2/ ├── conf/my.cnf └── data/

data目录不用手动创建,Docker挂载时会自动生成。conf/my.cnf必须手动创建,因为这是MySQL服务启动时加载配置的关键文件。我习惯把所有现网用过的配置文件保留在Git里,这样每次重建环境都有一份可追溯的基线。

2.4 主从配置文件

主库master/conf/my.cnf的内容:

[mysqld] server-id=1 log-bin=mysql-bin binlog-format=ROW gtid_mode=ON enforce_gtid_consistency=ON

从库slave1/conf/my.cnf和slave2/conf/my.cnf基本一样,只是server-id不同:

[mysqld] server-id=2 log-bin=mysql-bin binlog-format=ROW gtid_mode=ON enforce_gtid_consistency=ON read_only=ON super_read_only=ON

从库上我也开了binlog,而且加了read_only和super_read_only。这里解释一下为什么:从库开binlog是为了以后把它提升为主库时还有完整的日志链路;read_only防止业务写入从库,但仅仅read_only对root不生效,所以再加super_read_only,这样即使是超级用户也不能写,除非显式把super_read_only先关掉。

3. docker-compose配置与容器启动

3.1 docker-compose.yml完整内容

直接给出我最终使用的完整配置。为方便阅读,我把服务拆成三块,主库和两个从库都加上了healthcheck,这样后续编排可以基于健康状态做依赖等待。

services: master: image: mysql:8.0 container_name: mysql-master restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root123 ports: - "3306:3306" volumes: - ./master/conf/my.cnf:/etc/mysql/conf.d/my.cnf - ./master/data:/var/lib/mysql networks: - mysql-net healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"] interval: 5s retries: 10 slave1: image: mysql:8.0 container_name: mysql-slave1 restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root123 ports: - "3307:3306" volumes: - ./slave1/conf/my.cnf:/etc/mysql/conf.d/my.cnf - ./slave1/data:/var/lib/mysql networks: - mysql-net healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"] interval: 5s retries: 10 slave2: image: mysql:8.0 container_name: mysql-slave2 restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root123 ports: - "3308:3306" volumes: - ./slave2/conf/my.cnf:/etc/mysql/conf.d/my.cnf - ./slave2/data:/var/lib/mysql networks: - mysql-net healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"] interval: 5s retries: 10 networks: mysql-net: driver: bridge

注意这里把自定义配置文件挂载到/etc/mysql/conf.d/my.cnf,MySQL启动时会自动加载该目录下的所有.cnf文件,从而覆盖默认配置。还有一种方式是直接通过command传参,比如--server-id=1,效果一样,但我更推荐保留my.cnf,因为可读性更好,且后续改配置不需要动compose文件。

3.2 启动并检查容器

在mysql-cluster目录下执行:

docker compose up -d docker compose ps

正常情况下三个容器都是Up状态。如果某个容器启动失败,用日志定位:

docker compose logs master

常见启动失败原因是my.cnf文件权限或语法问题,比如mysql容器对挂载配置文件的属主要求比较严格,如果宿主机文件权限不对,MySQL可能直接拒绝启动。我遇到过文件是root:root且0644,容器内mysql用户也可以读,但部分发行版默认snap安装的Docker会有SELinux限制,导致挂载目录无法访问,这时候需要调整SELinux context,或者用-vz后缀,例如在volume配置里写./master/conf/my.cnf:/etc/mysql/conf.d/my.cnf:z

3.3 在主库创建复制账号

容器启动完成后,进入主库容器:

docker exec -it mysql-master bash mysql -uroot -proot123

然后创建复制专用账号。这一步很关键,如果使用MySQL 8.0,强烈建议用mysql_native_password来创建,避免从库I/O线程连不上:

CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'repl123'; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES;

REPLICATION SLAVE权限是必须的,REPLICATION CLIENT是为了让这个账号能执行SHOW MASTER STATUS之类的命令,方便排查问题。在生产环境,建议把账号网段限制得更严,比如'repl'@'192.168.1.%',而不是%

3.4 从库执行CHANGE MASTER

分别在两个从库容器里执行配置主库的命令:

docker exec -it mysql-slave1 bash mysql -uroot -proot123
STOP SLAVE; CHANGE MASTER TO MASTER_HOST='master', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='repl123', MASTER_AUTO_POSITION=1; START SLAVE;

slave2也执行同样命令。这里专门强调一个最容易踩的坑:MASTER_HOST必须写docker-compose网络里的服务名master,不是localhost,也不是宿主机IP。因为这是在容器内部连接主库容器,走的是docker内网。有人说我宿主机IP也能通,那是因为端口映射到了宿主机,但从库容器访问宿主机IP需要走网关,反而增加了不确定因素,服务名是最稳的。

4. 复制状态验证

4.1 查看关键状态字段

在从库执行:

SHOW SLAVE STATUS\G

重点关注这些字段:

字段名期望值说明
Slave_IO_RunningYesI/O线程是否在拉取日志
Slave_SQL_RunningYesSQL线程是否在回放日志
Seconds_Behind_Master0从库落后主库的秒数
Last_IO_Errno0I/O线程最近错误号
Last_SQL_Errno0SQL线程最近错误号
Retrieved_Gtid_Set非空从库已拉取的GTID集合

只要Snake_IO_Running和Slave_SQL_Running都是Yes,基本就说明复制建立成功了。Seconds_Behind_Master是0表示没有延迟,瞬时写入量大时这个值会跳动,只要稳定回落就不用紧张。

4.2 数据同步测试

验证复制是否真的生效,最直接的办法就是在主库建表写数据。

主库执行:

USE app_db; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ); INSERT INTO user(name) VALUES ('zhangsan'), ('lisi');

到从库1查询:

USE app_db; SELECT * FROM user;

如果能查到两条记录,说明同步正常。我再强调一个细节:从库的数据是异步复制过来的,刚执行完主库写入后立刻查询,理论上可能有毫秒级延迟,但通过GTID方式连接,一般不会出现查询不到的问题。

4.3 从库只读验证

为了确认从库真的不可写,我习惯直接做一次写入测试:

USE app_db; INSERT INTO user(name) VALUES ('wangwu');

预期会报错:The MySQL server is running with the --read-only option,这就说明read_only和super_read_only都生效了。有人在这里会发现root可以写,那是因为super_read_only没开,或者当前用户具备SUPER权限。所以配置文件中我特意把super_read_only=ON加上了。

5. 实际部署中的常见问题

5.1 I/O线程起不来,Last_IO_Errno 2061

这个问题在我早期搭8.0主从时频繁出现。报错日志里通常会有Authentication plugin 'caching_sha2_password' cannot be loaded之类的话。原因就是MySQL 8.0默认使用caching_sha2_password认证,而从库连接时用的客户端库可能不支持。

解决办法是我前面写的,创建复制账号时显式指定IDENTIFIED WITH mysql_native_password。虽然MySQL官方已经逐步推荐caching_sha2_password,但兼容性和复制的顺畅度才是这里的关键,开发测试环境没有必要上最严格的安全插件。

5.2 容器重启后复制中断

docker-compose环境经常因为重启或者系统更新导致容器重建。有些情况下重启后Slave_SQL_Running会变成No,或者从库一直卡在某个GTID不再前进。

根因往往是relay log上下文或者server-id冲突。我的排查顺序是:先看Last_SQL_Errno,如果数据量不大,最直接的办法是重建从库复制关系:

STOP SLAVE; RESET SLAVE ALL; CHANGE MASTER TO MASTER_HOST='master', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='repl123', MASTER_AUTO_POSITION=1; START SLAVE;

不要怕重建,GTID模式下从库会自动从主库最近的GTID位置开始拉取,不需要重新初始化数据。前提是从库数据目录没有脏数据,如果从库数据已经和主库不一致,那就需要重新灌一次基础数据。

5.3 端口映射导致的连接失败

这是一个看起来很低级但很容易犯的错。很多人在从库执行CHANGE MASTER时,MASTER_PORT写成宿主机映射出来的3307或3308,甚至写成3306但把MASTER_HOST写成宿主机IP,结果I/O线程一直报Last_IO_Errno 2003,无法连接主库。

原因很简单:从库容器和主库容器在同一个docker网络里,它们之间通信使用容器IP和服务名,端口永远是3306。宿主机映射的3307只是给宿主机或外部客户端使用的,容器内部不能拿这个端口去找主库。

5.4 SQL线程报1062或1032错误

这类错表示主从数据已经不一致了,可能是从库被手动写过数据,或者binlog回放时发生主键冲突。错误处理有个原则:不要一上来就执行SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1,这样会把问题掩盖掉。

我的做法是:先判断不一致的范围,如果只有个别事务出错,可以精准定位后手工补偿;如果错误很多,说明从库数据可信度已经很低,直接重建从库更省事。这里重建从库的代价远低于在脏数据上继续修补。

6. 生产环境下的进一步建议

6.1 数据卷与网络规划

用docker-compose搭主从,数据卷必须放在宿主机,别贪图方便写匿名卷,否则容器一删数据就没了。生产环境如果要跑MySQL,我建议不要用默认的docker0或者自定义bridge网络跑数据库集群,因为NAT转发对数据库这种长连接密集型应用不够友好。可以考虑network_mode: host,但代价是端口管理变复杂。

磁盘方面,如果条件允许,把数据目录放到独立的SSD盘上,避免和系统盘抢IO。

6.2 复制状态监控

主从复制配好只是起点,长期运行一定要监控两个关键线程。最简单的做法是写个脚本,每隔30秒在从库执行SHOW SLAVE STATUS,检查Slave_IO_Running和Slave_SQL_Running是否为Yes,不是就告警。

更专业一点,用Prometheus加mysqld_exporter,把Seconds_Behind_Master采集起来,配合Grafana画趋势图。这是一个典型的建设建议,但我在实践中发现很多人连第一层的脚本告警都没做,直到主从断了很久才发现,这种事故特别被动。

6.3 从库作为备份节点

一主二从搭建好以后,我习惯把备份任务放在从库上执行,避免备份影响主库性能。常用的备份命令是:

mysqldump -uroot -proot123 --single-transaction --source-data=2 app_db > backup.sql

这里要点一下:MySQL 8.0.26之前参数叫--master-data,之后改成了--source-data=2表示在备份文件的注释里记录binlog位置或GTID信息,方便后面用这份备份去搭建新的从库。如果要做更快速的物理备份,可以使用percona-xtrabackup,效果更好,但配置复杂度也更高。

6.4 读写分离的下一步延伸

一主二从搭完,如果业务想要真正减轻主库读压力,还需要在这套架构前面接入读写分离中间件。目前比较主流的选择是ProxySQL和ShardingSphere-Proxy。ProxySQL配置简单,对MySQL协议兼容性好,可以把读流量按权重分发到两个从库,写流量强制走主库。

我打算过阵子把ProxySQL加到这套docker-compose环境里,再把读写分离的效果测完发出来。到时候会发现,一主二从本身只是基础设施,上层路由才是完整落地的最后一公里。

最后再分享一个我自己的习惯:每次搭完这类环境,我都会在项目目录里放一份README,把关键账号、端口、复制命令、踩过的坑全写进去。等过三个月自己回来看,会发现这份README比任何博客都实用。这套一主二从环境我现在仍然在测试环境中使用,后续接上ProxySQL后我会再补一篇读写分离的实战文章。希望这篇配置过程能帮你少走一些弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 6:34:30

Python + requests:从零实现12306自动抢票脚本

简介:面向12306官网购票的自动抢票脚本,专门解决春运、节假日等高峰时段车票秒罄的痛点,通过模拟人工登录、余票查询和提交订单等操作,在放票第一时间完成购票请求,适合有一定Python基础、希望自动化抢票的用户。资源共…

作者头像 李华
网站建设 2026/9/1 6:33:59

2026年电脑电源选购指南:从核心参数到16款型号推荐

这次我们来看一篇2026年8月的电脑电源选购指南。对于装机用户来说,电源是决定整机稳定性的基石,选错了轻则重启蓝屏,重则可能带走其他硬件。这篇文章不聊虚的,直接给你一份覆盖50元到1200元价位的16款电源清单,并告诉你…

作者头像 李华
网站建设 2026/9/1 6:33:57

健身电商小程序

健身电商小程序 项目简介 这是一个完整的健身电商平台系统,包含微信小程序端和PC管理后台。系统实现了商品展示、购物车、订单管理、地址管理、商品评价、收藏等核心电商功能,采用前后端分离架构,适合作为微信小程序全栈开发学习项目或计算机…

作者头像 李华
网站建设 2026/9/1 6:33:24

飞轮式卫星姿态控制MATLAB仿真:M文件与Simulink双实现详解

简介:面向卫星姿态控制入门者的 MATLAB 飞轮仿真教学包,以飞轮为执行机构实现三轴稳定控制,基于经典 PID 控制器,动力学建模参考《航天器姿态动力学与控制》标准模型,适合理解状态方程推导与控制律编程。资源包共 14 个…

作者头像 李华
网站建设 2026/9/1 6:31:03

SAP S/4HANA ABAP开发实战教程:从核心语法到业务模块的完整学习路线

想学 SAP S/4HANA 开发,但面对海量的 ABAP 资料和复杂的系统概念,是不是感觉无从下手?网上教程要么是零散的代码片段,要么是晦涩的官方文档,真正能带你从零到一、贯穿核心业务场景的实战教程少之又少。更让人头疼的是&…

作者头像 李华
网站建设 2026/9/1 6:30:46

从零开发TCP/UDP调试工具:核心架构、代码实现与避坑指南

简介:TCP&UDP测试工具是一份免安装的网络调试实用程序,面向网络工程师、嵌入式开发人员和协议学习者,用于验证TCP/UDP通信、排查网络故障并评估传输性能。压缩包共13个文件,体积仅1.5MB,包含TCPUDPDbg.exe主程序、…

作者头像 李华