结论:本地单机用Docker跑一主一从是最可行、最接近生产逻辑的演练方式;手动在同一实例配主从或本地安装多个MySQL二进制包易遇端口冲突、日志混乱、启动失败等问题,不推荐。

直接说结论:本地单机用 Docker 跑一主一从是最可行、最接近生产逻辑的演练方式。硬要在同一台 MySQL 实例里配主从(比如改 server-id、开多个端口),会踩日志冲突、端口绑定失败、relay-log 权限混乱等一堆坑,不推荐。
为什么不用本地安装多个 MySQL 二进制包?
你可能会想:在 Windows/macOS 上手动解压多个 MySQL 目录,改不同端口、datadir、socket……但实际会遇到:
-
mysqld启动时抢pid文件或socket,报错Can't start server: Bind on TCP/IP port - Windows 下服务名冲突(
mysqld --install只能注册一个默认服务) - macOS/Linux 上
log-bin路径写错导致启动失败,且错误日志藏得深(/usr/local/mysql/data/hostname.err) - 从库
CHANGE MASTER TO后执行START SLAVE,Slave_IO_Running一直是Connecting——大概率是主库没监听0.0.0.0,只绑了127.0.0.1
Docker 方式:一主一从最小可行配置
用 docker-compose.yml 定义两个容器,隔离网络、端口、数据目录,避免所有路径和权限干扰。关键点不是“多”,而是“可验证”:
- 主库暴露
3306,从库暴露3307(方便本地mysql -h 127.0.0.1 -P 3307连) - 主库
server-id=1,从库server-id=2,必须不同且非 0 - 主库必须启用
log-bin且格式为ROW(binlog-format=ROW),否则某些 UPDATE 语句同步会丢数据 - 从库加
read_only=1,防止误写;但注意:root 用户仍可写,需额外super_read_only=1(MySQL 5.7+)才真正锁死
示例 docker-compose.yml 片段:
services:
mysql-master:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
command: >
--server-id=1
--log-bin=mysql-bin
--binlog-format=ROW
--binlog-do-db=test
volumes:
- ./master/conf:/etc/mysql/conf.d
<p>mysql-slave:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
ports:</p><ul><li>"3307:3306"
command: >
--server-id=2
--relay-log=mysql-relay
--read_only=1
--super_read_only=1
volumes:</li><li>./slave/conf:/etc/mysql/conf.d验证主从是否真通:别只看 SHOW SLAVE STATUS\G
SHOW SLAVE STATUS\G
很多人卡在这步:看到 Slave_IO_Running: Yes 和 Slave_SQL_Running: Yes 就以为成了。但实际可能:
-
Seconds_Behind_Master是NULL→ I/O 线程连上了,但还没开始拉日志(常见于主库没新写入) -
Seconds_Behind_Master是正数且持续增长 → 主从延迟,可能是从库磁盘慢、SQL 线程被锁表阻塞 - 主库
INSERT后,从库查不到 → 检查binlog-do-db是否漏配,或事务没提交(AUTOCOMMIT=0时没COMMIT)
可靠验证法:
- 主库建库
CREATE DATABASE test; - 主库建表
CREATE TABLE test.t1(id INT PRIMARY KEY, v VARCHAR(10)); - 主库插入
INSERT INTO test.t1 VALUES(1, 'a'); - 立刻从库查:
SELECT * FROM test.t1;—— 必须返回结果才算通
读写分离不是配完主从就自动发生
主从复制只解决“数据同步”,**读写分离需要额外组件或代码逻辑**。本地演练时,最容易落地的是:
-
应用层硬编码:Java 用
AbstractRoutingDataSource,Python 用sqlalchemy多引擎 + 自定义路由函数。优点是零依赖,缺点是改业务代码 -
MyCat 或 ProxySQL 本地容器化:再起一个容器跑 MyCat,配置
schema.xml把writeHost指向mysql-master:3306,readHost指向mysql-slave:3306。应用只连 MyCat 的8066端口即可 -
跳过中间件,用连接字符串区分:开发时写两套 DB 配置,读操作用
jdbc:mysql://127.0.0.1:3307/...,写操作用jdbc:mysql://127.0.0.1:3306/...。简单粗暴,适合验证逻辑
注意:如果用 MyCat,它默认把 SELECT 全发给从库,但 SELECT ... FOR UPDATE 会走主库——这点必须测试,否则事务一致性崩塌。
真正的难点不在配置,而在于主从延迟不可控:你刚在主库写完,立刻去从库读,可能读不到。业务里所有“写后立即读”的场景(比如注册后跳转个人页),必须强制走主库,或者加缓存兜底。这个细节,90% 的本地演练会忽略。


















