Docker快速搭建MySQL 8主从复制:从原理到实践全解析
发布时间:2026/10/9 8:46:30 锦皓数字建站

从开发环境到生产演练MySQL 的主从复制一直是数据库高可用架构里绕不开的一环。过去要搭一套主从得准备两台物理机或者虚拟机装系统、配环境、调权限光想想就头大。现在用 Docker两条命令就能跑起两个 MySQL 8 实例十几分钟完成主从搭建。这篇文章我把完整过程拆开揉碎讲清楚从 Docker 环境准备到主从复制配置再到问题排查手把手带你从头走一遍。1. 为什么用 Docker 搭 MySQL 8 主从复制1.1 主从复制到底解决什么问题先说说主从复制本身。MySQL 的主从复制机制简单来理解就是一台主库Master负责处理写操作同时把所有变更记录到二进制日志binlog里一台或多台从库Slave通过网络连接主库拉取 binlog 并重放到本地从而让从库的数据和主库保持一致。这套机制核心解决三类问题。第一是读写分离读请求打到从库写请求打到主库分摊数据库压力。第二是容灾备份主库宕机了可以迅速把从库提升为主库缩短业务中断时间。第三是数据分析场景让 BI 报表、定时任务去查从库不影响主库性能。很多人问MySQL 8 相比 5.7 在主从复制上有什么变化最直观的一点是身份认证插件默认从 mysql_native_password 换成了 caching_sha2_password这导致旧版本惯用的复制账号写法需要调整。另一个重点是 MySQL 8 对 GTID全局事务标识模式支持更成熟后面我会讲怎么用 GTID 方式做复制比传统基于日志文件和位置的复制省心很多。1.2 为什么选 Docker 而不是直接装两台 MySQL你可能会说我直接用两台服务器装 MySQL 不就行了可以但代价不小。你需要先准备两台机器安装依赖包、配置环境变量、管理服务生命周期每台机器都要重复一遍。更麻烦的是如果只是本地开发或者学学原理根本没有必要动用两台物理机。Docker 在这个场景下的优势非常明显。首先是环境隔离两个 MySQL 容器互不影响各自拥有独立的文件系统和网络栈模拟的就是两台独立服务器的效果。其次是启动速度快镜像拉下来之后秒级启动容器随时可以销毁重建试错成本几乎为零。第三是版本可控想用 MySQL 8.0.36直接指定 tag 拉取就行不用考虑宿主机的包管理混乱问题。还有一点很重要就是一致性问题。用 Docker 镜像启动的 MySQL环境是固定的不会因为宿主机是 CentOS 还是 Ubuntu 而产生差异。你在自己电脑上验证通过的配置扔到服务器上同样能跑通。我自己最初学主从复制时就是在本地一台 Mac 上用 Docker 同时跑了三个 MySQL 容器一主两从效果和真实多机环境几乎没差。2. 环境准备先把地基打好2.1 Docker 安装与基础验证不管你是 Windows、macOS 还是 Linux第一步都是先把 Docker 装好。Windows 和 macOS 用户直接装 Docker Desktop装完启动即可。Linux 用户根据发行版不同用对应包管理器安装 docker-ce 或者 docker.io。装完先别急着拉镜像先做个健康检查。打开终端执行docker --version sudo systemctl status docker # Linux 下检查守护进程状态如果提示Cannot connect to the Docker daemon或者permission denied while trying to connect to the docker api大概率是两种情况一是 Docker 守护进程根本没启动Linux 下执行sudo systemctl start docker就行二是当前用户不在 docker 用户组里执行sudo usermod -aG docker $USER然后重新登录终端即可。这个坑我在 Ubuntu 上踩过好几次明明 docker 命令存在就是连接不上其实只是权限问题。确认 Docker 正常工作后顺手验证一下容器运行能力docker run hello-world能正常输出 Hello from Docker 就说明基础环境没问题了。如果你是在内网环境或者国内网络环境下拉镜像很慢别忘了配置镜像加速器编辑 Docker 的 daemon.json 文件添加 registry-mirrors 配置。虽然这里不展开讲加速器怎么获取但这个方法在拉取 MySQL 8 镜像时能帮你省下大量时间。2.2 拉取 MySQL 8 镜像的注意事项MySQL 官方镜像一直维护得很好我们直接使用官方镜像最省心。执行docker pull mysql:8.0这里我强烈建议使用mysql:8.0这个 tag而不是不带 tag 的mysql:latest。因为 latest 意味着版本不固定可能今天拉的是 8.0.x过段时间再拉就变成 8.x.y版本变动带来的配置差异会让人抓狂。锁定大版本号保证可复现性。拉取完成后运行docker images确认镜像存在。如果镜像下载慢或者超时可以尝试重新拉取或者换一个网络环境。镜像本身大约 500MB 左右包含完整的 MySQL 8 服务器和客户端工具这个体积在数据库镜像里不算大。有一个细节值得注意MySQL 8 官方镜像默认会自带一些优化配置但它不会自动创建 my.cnf 自定义配置。如果你不加任何参数直接启动容器MySQL 会以默认配置运行。而我们做主从复制必须修改 server-id、开启 binlog 等这就要求我们在容器启动时挂载自定义配置文件或者进入容器后手动修改。下面两部分我会详细演示。3. 主从节点容器创建全流程3.1 主库容器启动与参数解析先创建主库容器。为了模拟独立的服务器环境我给每个容器都设置了独立的端口映射和命名这样既可以通过 docker 内部网络互相访问也能从宿主机分别连接两个 MySQL 实例。直接看命令docker run -d \ --name mysql-master \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -v /data/mysql-master:/var/lib/mysql \ -v /data/mysql-master-conf:/etc/mysql/conf.d \ mysql:8.0逐项拆解一下各个参数的作用。-d表示后台运行容器--name mysql-master给容器起个明确的名字后续所有操作都可以通过这个名字引用容器。-p 3307:3306把宿主机的 3307 端口映射到容器的 3306 端口这样宿主机上通过 3307 端口访问的就是主库容器避免和宿主机上可能已有的 MySQL 3306 端口冲突。-e MYSQL_ROOT_PASSWORDroot123是设置 MySQL root 用户的初始密码第一次启动容器时生效。数据卷挂载非常重要。-v /data/mysql-master:/var/lib/mysql是把宿主机的目录映射到容器内部 MySQL 数据目录这样即使容器被删除数据仍然保留在宿主机上。-v /data/mysql-master-conf:/etc/mysql/conf.d是把我们准备好的配置文件目录挂载进容器MySQL 8 官方镜像会自动加载/etc/mysql/conf.d下所有的.cnf文件。记住后续主从复制的关键配置就放在这里。启动完成后验证容器是否正常运行docker ps docker exec -it mysql-master mysql -uroot -proot123能成功进入 MySQL 命令行说明主库容器工作正常。顺便执行SELECT VERSION();确认一下版本号确保确实是 8.x。3.2 从库容器启动要点从库容器的启动命令基本一致但有三个关键区别。第一是容器名不同我用mysql-slave。第二是端口映射不同从库映射到宿主机 3308 端口。第三也是经常被忽略的一点如果两个 MySQL 容器共用同一个宿主机目录作为数据卷会导致数据目录锁冲突。所以从库必须使用独立的宿主机数据目录。docker run -d \ --name mysql-slave \ -p 3308:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -v /data/mysql-slave:/var/lib/mysql \ -v /data/mysql-slave-conf:/etc/mysql/conf.d \ mysql:8.0这里我建议先把两个容器的 my.cnf 配置目录都准备好配置文件的内容下面部分会详细写。也就是说在docker run之前先在宿主机上创建好/data/mysql-master-conf和/data/mysql-slave-conf目录把对应的配置文件放进去再启动容器。这样容器一启动配置就生效了不用二次修改。另外要注意两个容器要能互相通信最简单的方式是让它们在同一个 docker 网络里。默认情况下用docker run启动的容器会自动加入bridge网络所以 name 参数指定的容器名就是网络内的主机名。例如从库容器内可以通过mysql-master:3306访问主库容器后面的 CHANGE MASTER TO 命令就会用到这个特性。4. 主从复制配置核心环节4.1 主库 my.cnf 配置与 binlog 开启进入正题。主从复制的核心原理就是主库记录 binlog从库读取并重放 binlog。所以第一步在主库的 my.cnf 里开启 binlog并配置一个唯一的 server-id。在主库的配置挂载目录里创建my.cnf文件也就是宿主机上的/data/mysql-master-conf/my.cnf内容如下[mysqld] server-id1 log-binmysql-bin binlog_formatROW gtid_modeON enforce_gtid_consistencyON逐项解释。server-id1是主库的全局唯一 ID从库必须使用不同的值这是 MySQL 主从复制的硬性要求如果两端 server-id 相同从库会报错并拒绝同步。log-binmysql-bin指定二进制日志文件的前缀MySQL 会自动生成 mysql-bin.000001、mysql-bin.000002 等文件。binlog_formatROW设置日志记录格式为行级相比 STATEMENT 格式ROW 格式能精确记录每一行数据的变化对数据一致性更友好也是 MySQL 8 官方推荐的配置。gtid_modeON配合enforce_gtid_consistencyON开启 GTID 模式。GTID 是 MySQL 5.6 引入的事务全局标识每个事务都有唯一的 ID从库通过 GTID 就能判断哪些事务已经执行过、哪些还需要同步不用担心日志文件名和位置错乱的问题。配置文件写好之后需要重启主库容器让配置生效docker restart mysql-master重启后进入 MySQL验证 binlog 是否正常开启SHOW VARIABLES LIKE log_bin; SHOW VARIABLES LIKE gtid_mode;两个值都应该显示 ON。这里插一句如果你不想用 GTID也可以基于日志文件位置做传统复制。但是我的经验是从 MySQL 8 开始GTID 模式已经非常成熟稳定了新搭的主从直接上 GTID 就行后面故障切换时 GTID 的优势非常明显。不建议再学老一套的日志位置方式除非你在维护存量系统。4.2 创建复制专用账号与权限隔离主库需要创建一个专门用于复制的账号从库会拿这个账号去连接主库拉取 binlog。出于安全考虑这个账号最好只授予复制权限不要给超级权限。在 MySQL 8 中执行以下 SQLCREATE USER repl% IDENTIFIED BY repl123; GRANT REPLICATION SLAVE ON *.* TO repl%; FLUSH PRIVILEGES;注意MySQL 8 默认的认证插件是caching_sha2_password这和我们第 1 节提到的变化有关。使用CREATE USER语句创建的用户默认就采用这个插件。在主从复制场景下从库连接主库时同样需要认证而caching_sha2_password在非 SSL 连接下需要额外交换公钥可能会引发连接失败。解决办法有两种。第一种是创建用户时显式指定使用mysql_native_password插件CREATE USER repl% IDENTIFIED WITH mysql_native_password BY repl123;第二种是保持默认插件然后在从库的 CHANGE MASTER TO 命令里加上GET_MASTER_PUBLIC_KEY1选项。我个人更推荐第一种因为 mysql_native_password 在复制场景下兼容性更好配置简单不依赖 SSL。需要注意的是MySQL 8.4 开始已经移除了 mysql_native_password 插件如果你用的是 8.4 以上版本就得改用第二种方式或者配置 SSL。不过目前主流的 8.0 版本仍然支持这种方法我演示用的就是 8.0。账号创建完成后查看一下主库当前 binlog 文件和位置。虽然我们用 GTID 模式不需要像传统模式那样手动记录日志文件名和位置但SHOW MASTER STATUS;仍然可以用来确认 binlog 已经开启并查看当前 GTID 的执行情况。在 GTID 模式下这条命令输出的Executed_Gtid_Set列会显示已经执行过的事务集合。4.3 从库 my.cnf 配置与 CHANGE MASTER TO从库的配置文件同样放在它的配置挂载目录里也就是宿主机/data/mysql-slave-conf/my.cnf[mysqld] server-id2 log-binmysql-bin binlog_formatROW gtid_modeON enforce_gtid_consistencyON read_onlyONserver-id2和主库区分开。从库也开启 binlog 的原因是为了方便后续的级联复制即从库还可以继续作为更低一级从库的主库。这在多级复制架构中很实用。read_onlyON则是保护机制防止有人直接连上从库写数据导致主从数据不一致。注意 read_only 对 root 用户不生效所以不影响管理员操作。配置完之后同样重启从库容器docker restart mysql-slave重启完成后进入从库的 MySQL 命令行执行 CHANGE MASTER TO 命令CHANGE MASTER TO MASTER_HOSTmysql-master, MASTER_PORT3306, MASTER_USERrepl, MASTER_PASSWORDrepl123, MASTER_AUTO_POSITION1;这里有几个细节必须讲清楚。MASTER_HOSTmysql-master用的是容器名因为从库容器和主库容器在同一个 docker bridge 网络内互相之间可以直接通过容器名访问。如果你不了解这一点填了宿主机的 IP也不是不行但宿主机的 3307 端口映射的是主库从库容器通过宿主机 IP 也能访问到不过这就多绕了一层而且如果宿主机防火墙拦截反而会失败。直接用容器名是最干净的方式。MASTER_PORT3306是主库容器内部的端口不是宿主机的 3307因为这是容器之间通信不经过端口映射。MASTER_AUTO_POSITION1是 GTID 模式的核心选项告诉从库自动从主库同步 GTID 位置不需要手动指定日志文件和位置。设置完主库信息后启动复制线程START SLAVE;然后检查复制状态SHOW SLAVE STATUS\G重点看两行输出Slave_IO_Running: Yes和Slave_SQL_Running: Yes。两个都显示 Yes 说明复制进程正常工作。IO 线程负责连接主库拉取 binlogSQL 线程负责把拉取下来的日志事件重放到本地。任何一个不是 Yes都需要根据下面的报错信息排查。此外还要看Seconds_Behind_Master字段表示从库落后主库的秒数在正常同步状态下这个值应该接近 0。到这一步主从复制的配置已经完成。接下来验证复制是否真的生效。5. 验证复制效果与问题排查实录5.1 数据同步验证三板斧配置完成不等于万事大吉必须实际验证一下数据能不能从主库同步到从库。我的验证思路分三步走第一步在主库创建测试数据库和表CREATE DATABASE testdb; USE testdb; CREATE TABLE user_info ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );第二步插入一条测试数据INSERT INTO user_info (name) VALUES (replication_test);第三步切换到从库查询USE testdb; SELECT * FROM user_info;能从从库里查到replication_test这条记录说明主从复制全链路已经打通。这里要注意查询前先确认当前连接的是从库的 3308 端口。很多人在本地测试时因为只映射了一个端口结果主从都连到了同一个实例自然会发现数据都有以为是复制成功了其实是连错库了。还有一个细节可以加强验证就是在从库查看 binlog 的同步情况。执行SHOW BINLOG EVENTS能看到从库本地也记录了和主库一致的事件。这能进一步确认 binlog 的复制粒度是正确的。5.2 高频问题与排查思路速查表实践过程中总会遇到各种报错我把最常出现的问题和排查思路整理成了一张表方便你对照定位。现象可能原因排查与解决Slave_IO_Running 为 Connecting网络不通或账号认证失败从库容器内执行ping mysql-master测试连通性进入主库确认 repl 账号存在且密码正确确认主库 bind-address 没有限制连接Slave_IO_Running 为 No认证插件不兼容按 4.2 节方法重建 repl 账号指定 mysql_native_password 插件Slave_SQL_Running 为 No从库 SQL 线程执行报错查看Last_SQL_Error字段常见的如主键冲突、表结构不一致针对具体表修复后执行STOP SLAVE; SET GLOBAL SQL_SLAVE_SKIP_COUNTER1; START SLAVE;谨慎跳过复制状态正常但数据不同步主从配置不一致或漏改配置确认主从 my.cnf 都开启了 binlog 和 GTID确认 server-id 不同确认从库 read_onlyON 防止误写容器重启后复制断开容器 IP 变化导致从库连不上原主库使用容器名作为 MASTER_HOST而不是 IP 地址或者使用 docker network 固定 IPSHOW MASTER STATUS 为空binlog 未开启检查 my.cnf 中 log-bin 配置是否生效注意 MySQL 配置文件路径是否正确docker exec进入容器执行SHOW VARIABLES LIKE log_bin;在这些问题里最有代表性的当属Slave_IO_Running卡在 Connecting 状态。我排查过最多的情况是账号认证失败常见症状是Last_IO_Error输出Access denied for user repl...。原因有两个一是密码写错了二是缓存认证插件问题。尤其是 MySQL 8默认 caching_sha2_password 插件在非 SSL 链路上第一次连接需要交换公钥如果 CHANGE MASTER TO 命令里没有指定相关选项连接就会失败。另一个容易踩的坑是忘记在宿主机上提前创建挂载目录导致 Docker 自动以 root 权限创建目录容器内的 mysqld 进程没有权限写入数据文件报Permission denied。解决办法是在docker run之前手动mkdir -p /data/mysql-master /data/mysql-master-conf然后给当前用户授权再启动容器。5.3 进阶从库作为备份节点的日常管理技巧主从搭建好之后日常管理还有很多细节可以优化。比如从库默认是 read_only 模式但如果你需要暂时让从库接受写操作去修复某条数据可以临时关闭 read_only操作完再打开SET GLOBAL read_onlyOFF; -- 执行修复操作 SET GLOBAL read_onlyON;再比如监控复制是否正常可以写一个简单的 shell 脚本定时执行SHOW SLAVE STATUS并检查Slave_IO_Running和Slave_SQL_Running是否都为 Yes不是就报警。生产环境不推荐人工盯屏脚本化监控才是正道。另一个实用技巧是定期在主库执行FLUSH LOGS让 binlog 文件按时间分割方便定期归档和清理。binlog 文件无限制增长会占满磁盘空间MySQL 8 默认会保留一定天数但如果你设置了binlog_expire_logs_seconds参数就可以精确控制保留时长。在主从复制场景里如果从库长期离线或者网络中断binlog 保留太短可能导致从库恢复时找不到对应日志这个参数需要综合考虑从库的容忍度来设置。6. 写在最后几个值得参考的经验沉淀这套 Docker MySQL 8 的主从复制方案我在本地开发环境、测试环境和轻量生产环境都跑过整体非常稳定。最后分享几个我觉得特别值得沉淀的经验。第一如果你打算把这套方案用于生产容器一定要固定 IP 或者使用容器名通信千万别在 CHANGE MASTER TO 里写死容器 IP。Docker 容器每次重启 IP 都可能变一旦变了从库和主库的连接就断了。用容器名通信可以规避这个问题因为 Docker 内置 DNS 会自动解析容器当前最新的 IP。第二GTID 模式初期搭建时尽量区分两种场景。如果是从零开始搭主从CHANGE MASTER TO 使用MASTER_AUTO_POSITION1就行。但如果是从已有数据的主库迁移到从库需要先把主库数据备份恢复到从库并保留 GTID 信息再启动复制。这个步骤遗漏了从库会从最开始追赶主库的所有事务耗时很长。正确的做法是用mysqldump --set-gtid-purgedON导出备份然后导入从库再启动复制。第三关于密码管理。上面为了演示用了简单的root123和repl123生产环境务必使用强密码并且考虑禁用 root 远程登录只保留复制账号和必要的管理账号。Docker 的-e MYSQL_ROOT_PASSWORD参数只是初始化时设置密码容器启动后修改 root 密码不会影响环境变量里记录的旧值这一点不要搞混。第四Docker 容器的时间问题。默认情况下容器使用 UTC 时区如果 MySQL 里 TIMESTAMP 字段存储的时间比北京时间少 8 个小时不要惊讶。主库和从库如果时区不一致虽然不影响复制但查询出来的时间会让人困惑。建议在 my.cnf 里加上default-time-zone08:00根据你实际时区调整或者在 docker run 时加-e TZAsia/Shanghai让容器时区和宿主保持一致。第五也是我个人感受最深的一点先理解原理再动手配置。很多人照着教程一步步执行成功之后还是会糊里糊涂。我强烈建议你搭好之后主动破坏一次复制比如在从库上删掉一条主库存在的数据或者手动插入一条与主库冲突的数据然后观察复制状态会发生什么变化。这个破坏性实验能帮你真正理解主从复制的容错机制远比跑通一遍教程有价值。我把这个习惯叫作故障演练一次主动演练的价值抵得上十次被动排查。到这里整个 Docker 搭建 MySQL 8 主从复制的流程就完整走完了。从镜像拉取、容器创建、配置文件编写到复制账号创建、CHANGE MASTER TO 配置、数据同步验证每个环节都涉及了具体命令和背后的原理。希望这篇文章能帮你少踩几个坑把主从复制顺畅跑起来。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。