数据库通常是整个系统里最不能丢的一层。单台 MySQL 服务器一旦磁盘损坏、误删数据或硬件故障,业务数据就可能永久丢失。要解决这个问题,最基础也最重要的手段就是主从复制:把数据实时同步到另一台服务器上,主库负责写,从库负责读和备份,主库故障时还能快速切换。
这篇文章手把手带你搭一套 MySQL 主从复制,从复制原理讲起,一步步配置 server-id、log-bin、GTID,创建复制账号并授权 REPLICATION SLAVE,用 mysqldump 做一致性导出和导入,最后执行 CHANGE MASTER TO 和 START SLAVE 启动复制。重点会解读 SHOW SLAVE STATUS 的每一个关键字段,告诉你 Slave_IO_Running、Slave_SQL_Running、Seconds_Behind_Master 分别意味着什么,并给出半同步复制的思路和 1062 主键冲突、认证插件等常见错误的排查方法。所有命令均可复制修改后使用。
主从复制是怎么工作的
MySQL 主从复制的核心机制是二进制日志(binlog)。主库把所有写操作记录成事件写进 binlog;从库上有一个 IO 线程,连接到主库请求 binlog 内容,收到后写入本地的中继日志(relay log);从库上还有一个 SQL 线程,读取 relay log 里的事件并重放,从而让从库的数据与主库保持一致。
所以从库的状态可以用两个线程的健康度来判断:IO 线程负责"拉取日志",SQL 线程负责"重放日志"。这两个线程任何一个停掉,复制都会出问题。理解"binlog → relay log → 重放"这条链路,是排查复制故障的基础。
复制有三种日志格式:STATEMENT 记录 SQL 语句本身,日志小但在某些不确定函数下可能导致主从不一致;ROW 记录行的变化,一致性最好但日志量大;MIXED 由 MySQL 自行选择,通常能兼顾。现代生产环境更推荐 ROW 格式,配合 binlog 的完整行信息,主从一致性更有保障。
另一个重要概念是 GTID(全局事务标识符)。开启 GTID 后,每个事务在主库上都有一个全局唯一的编号,从库记录已执行过的 GTID,切换主库或重搭复制时不用再计算 binlog 文件名和位置,运维更简单。新版本 MySQL 默认开启 GTID,这是主从配置里推荐的方式。
环境规划与前置准备
示例环境如下,请替换成你自己的 IP 和密码:
| 角色 | 主机名 | IP | 说明 |
|---|---|---|---|
| 主库 MASTER | db1 | 192.0.2.31 | 接受读写 |
| 从库 SLAVE | db2 | 192.0.2.32 | 只读、备份、切换候选 |
两台机器要安装同版本或兼容版本的 MySQL。数据库版本不一致(尤其大版本差异)会导致复制兼容性问题,建议主从版本一致。RHEL/CentOS/Rocky 系可以用发行版仓库或 MySQL 官方仓库安装 MySQL 8:
# RHEL / CentOS / Rocky 系
sudo dnf install -y mysql-server
sudo systemctl enable --now mysqld
Debian/Ubuntu 系:
# Debian / Ubuntu 系
sudo apt update
sudo apt install -y mysql-server
sudo systemctl enable --now mysql
安装后运行安全初始化,设置 root 密码、移除匿名用户和测试库:
# 两类系统通用
sudo mysql_secure_installation
配置前先确认时间同步,两台机器的时间偏差会影响 Seconds_Behind_Master 的判断,建议都启用 chrony 或 ntpd。还要规划好防火墙:主库的 3306 端口只应允许从库的内网地址访问,不要对公网开放。RHEL 系用 firewalld:
# RHEL / CentOS / Rocky 系,仅允许从库 IP 访问 3306
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.0.2.32/32" port protocol="tcp" port="3306" accept'
sudo firewall-cmd --reload
Debian/Ubuntu 系用 ufw:
# Debian / Ubuntu 系
sudo ufw allow from 192.0.2.32 to any port 3306 proto tcp
sudo ufw status verbose
风险提示: 修改防火墙前确认 SSH 端口已放行,并保留控制台访问手段,避免误封失联。
配置主库:server-id、log-bin 与 GTID
MySQL 8 的配置文件通常是 /etc/my.cnf(RHEL 系)或 /etc/mysql/mysql.conf.d/mysqld.cnf(Debian 系)。在 [mysqld] 段下添加主库配置:
[mysqld]
server-id = 1
log_bin = mysql-bin
binlog_format = ROW
gtid_mode = ON
enforce_gtid_consistency = ON
log_replica_updates = ON
binlog_expire_logs_seconds = 604800
参数含义要理解清楚。server-id 在主从中必须唯一,主库用 1、从库用 2 是最简单的做法。log_bin 指定 binlog 文件名前缀。binlog_format = ROW 使用行格式。gtid_mode = ON 和 enforce_gtid_consistency = ON 开启 GTID 并强制一致性。log_replica_updates = ON 在从库上(旧版本叫 log_slave_updates)让从库把重放的事件也写进自己的 binlog,便于级联复制和切换。binlog_expire_logs_seconds 控制 binlog 保留时间,默认约 30 天,可根据磁盘调整。
改完重启主库:
sudo systemctl restart mysqld # RHEL 系
sudo systemctl restart mysql # Debian 系
确认配置生效:
SHOW VARIABLES LIKE 'server_id';
SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'gtid_mode';
创建复制账号
在主库上创建专用于复制的账号,并授予 REPLICATION SLAVE 权限。注意 3306 只允许从库 IP 访问,账号也应限制来源:
CREATE USER 'repl'@'192.0.2.32' IDENTIFIED BY 'REPLACE_WITH_STRONG_PASSWORD';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.0.2.32';
FLUSH PRIVILEGES;
MySQL 8 默认认证插件是 caching_sha2_password。如果从库版本较老或客户端不支持,可能需要在创建账号时指定 mysql_native_password:
CREATE USER 'repl'@'192.0.2.32' IDENTIFIED WITH mysql_native_password BY 'REPLACE_WITH_STRONG_PASSWORD';
不过更推荐的做法是升级客户端和从库,而不是退回到旧认证方式。
用 mysqldump 做一致性导出与导入
从库要做的第一件事是把主库的现有数据完整复制过来,并且记录复制起点。若使用 GTID,导出时会带上 GTID 信息,导入后从库能自动按 GTID 找位置。为了一致性导出,需要加 --single-transaction(InnoDB)和 --master-data(或 --source-data,MySQL 8.0.26 起改名):
# 在主库执行一致性备份
mysqldump -u root -p \
--single-transaction \
--source-data=2 \
--routines --triggers --events \
--all-databases > /tmp/full_backup.sql
关键参数解释:--single-transaction 在 InnoDB 上通过一致性快照导出,不锁表;--source-data=2 把 binlog 位置以注释形式写进备份,便于记录起点(旧版本用 --master-data);--routines --triggers --events 一并导出存储过程、触发器和事件。
把备份传送到从库并导入:
# 传到从库
scp /tmp/full_backup.sql root@192.0.2.32:/tmp/
# 在从库导入
mysql -u root -p < /tmp/full_backup.sql
如果数据量很大,导入会耗时较久,期间不要中断。导入完成后,确认表和数据都在,再进行复制配置。风险提示: 导入会覆盖同名数据库的数据,操作前务必确认从库是干净或可覆盖的,并对现有数据做备份。
在从库上启动复制
先给从库配置 server-id 和 GTID:
[mysqld]
server-id = 2
log_bin = mysql-bin
binlog_format = ROW
gtid_mode = ON
enforce_gtid_consistency = ON
relay_log = relay-bin
read_only = ON
read_only = ON 可以把从库设为只读,防止误写。需要说明的是,read_only 对拥有 SUPER 权限的用户无效,MySQL 8 可用 super_read_only = ON 进一步限制。重启从库后,用 CHANGE MASTER TO 指定主库和复制账号。MySQL 8.0.23 起推荐用 CHANGE REPLICATION SOURCE TO,旧命令 CHANGE MASTER TO 仍兼容:
-- 使用 GTID 自动定位
CHANGE MASTER TO
MASTER_HOST = '192.0.2.31',
MASTER_PORT = 3306,
MASTER_USER = 'repl',
MASTER_PASSWORD = 'REPLACE_WITH_STRONG_PASSWORD',
MASTER_AUTO_POSITION = 1;
START SLAVE;
若未开 GTID,则要显式指定 FILE 和 POSITION,位置来自导出文件头部的注释(--source-data=2 写入的位置):
CHANGE MASTER TO
MASTER_HOST = '192.0.2.31',
MASTER_USER = 'repl',
MASTER_PASSWORD = 'REPLACE_WITH_STRONG_PASSWORD',
MASTER_LOG_FILE = 'mysql-bin.000001',
MASTER_LOG_POS = 12345;
START SLAVE;
MySQL 8.0.23 及以后的新写法是把 MASTER_ 换成 SOURCE_、START SLAVE 换成 START REPLICA,两者目前等价。建议在新环境统一用新语法。
解读 SHOW SLAVE STATUS 关键字段
查看复制状态:
SHOW SLAVE STATUS\G
输出字段很多,重点看以下几个。Slave_IO_State 表示 IO 线程当前状态,正常应与主库保持连接。Slave_IO_Running 和 Slave_SQL_Running 都应为 Yes,任何一个为 No 都说明复制异常。Master_Log_File 和 Read_Master_Log_Pos 是 IO 线程读到的主库位置,Relay_Master_Log_File 和 Exec_Master_Log_Pos 是 SQL 线程已执行的位置,两者越接近说明延迟越小。
Seconds_Behind_Master 是最常被关注的延迟指标,但它只看 IO 和 SQL 之间的时间差,主库空闲时可能显示 0 而实际从库落后很多,不能完全依赖。Last_IO_Error 和 Last_SQL_Error 记录最近的错误,排查必看。Retrieved_Gtid_Set 和 Executed_Gtid_Set 则反映已拉取和已执行的 GTID 集合。
下面是一个正常状态的关键片段示意:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_IO_Error:
Last_SQL_Error:
如果 Slave_IO_Running: Connecting,通常是网络不通或账号密码错;如果是 No 并伴随错误信息,要看 Last_IO_Error。三个字段同时正常,才说明复制链路健康。
半同步复制思路
默认的异步复制下,主库提交事务后不等从库确认就返回成功,主库宕机时最后一部分事务可能还没传到从库,造成数据丢失。半同步复制(semi-sync)要求主库在提交时至少等待一个从库确认收到 binlog 后才返回,能显著降低丢数据的风险,代价是增加一点提交延迟。
半同步通过插件实现。主库安装 rpl_semi_sync_master 插件并开启,从库安装 rpl_semi_sync_slave 插件并开启,然后设置超时:
-- 主库
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = 1;
SET GLOBAL rpl_semi_sync_master_timeout = 1000; -- 毫秒,超时后退回异步
-- 从库
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
SET GLOBAL rpl_semi_sync_slave_enabled = 1;
注意超时后退回异步是它的设计,意味着极端情况下仍可能退化。要做得更强,需要引入组复制(Group Replication)或 MGR、Galera 这类方案,但复杂度和运维成本也更高。对多数中小业务,异步或半同步复制已经够用。
常见报错与排查
1062 主键冲突
从库重放时如果遇到 Duplicate entry 错误,通常是主从数据已不一致,比如有人直接往从库写了数据。排查步骤是先确认是哪个表、哪条记录冲突,评估是丢弃从库数据还是跳过事务。可用 SET GLOBAL SQL_SLAVE_SKIP_COUNTER 跳过,但跳过会导致数据进一步不一致。更稳妥的做法是重新做一次全量导出导入,重建复制。风险提示: 跳过事务会破坏一致性,仅作为临时手段,事后必须重新同步。
认证插件错误
如果 IO 线程报认证相关错误,多半是 caching_sha2_password 与旧客户端不兼容。解决方案是升级客户端,或重建账号并指定 mysql_native_password。不要为了省事把整个实例的默认认证方式改回旧插件。
复制中断后如何恢复
先看 Last_IO_Error 和 Last_SQL_Error 定位原因,修复后用 START SLAVE 重试。如果是因为 binlog 被清理(binlog_expire_logs_seconds 太小导致 IO 线程要的位置已经不存在),需要重新做全量同步。所以 binlog 保留时间要足够长。
从库延迟越来越大
先看 Seconds_Behind_Master 和是否单线程重放。MySQL 8 支持并行复制,可开启 replica_parallel_workers 和 replica_parallel_type = LOGICAL_CLOCK 提升重放速度。也要检查从库是否有大查询占用资源。
常见问题
主从复制能自动切换吗?
不能。复制本身只负责同步数据,切换需要额外的工具或人工,比如 MHA、Orchestrator、MySQL Router 或云数据库的自动切换能力。
从库可以写入吗?
默认从库可写,但会破坏复制。应设置 read_only = ON,必要时用 super_read_only = ON 限制超级用户。
GTID 和传统位置复制哪个好?
新环境优先用 GTID。它让复制位置自动跟踪,切换和重建复制时更简单,不容易因为文件名和位置算错而出错。
复制是加密的吗?
默认不加密。跨公网或不安全网络复制时,应启用 TLS 加密连接,或通过内网、VPN 传输。
从库能当备份用吗?
不能替代备份。误删主库数据会同步到从库,从库也会一起丢。备份要独立,且要异地保存并定期做恢复演练。
总结
- 主从复制靠 binlog 拉取加 relay log 重放实现,IO 线程和 SQL 线程都健康才算复制正常。
- 主库配置 server-id、log-bin、ROW 格式和 GTID,从库用不同 server-id 并设 read_only,两端版本尽量一致。
- 用 mysqldump 加
--single-transaction和--source-data做一致性导出,导入后通过 GTID 自动定位或显式指定位置。 - SHOW SLAVE STATUS 重点看两个 Running、Seconds_Behind_Master 和 Last_Error 字段,延迟指标不能只看这一个值。
- 半同步能降低丢数据风险,复制本身不负责切换,切换、备份和演练都需要单独设计。
企业QQ咨询




