Skip to content

13|主从复制

前面所有操作都在一台 MySQL 上。但一台机器挂了,数据库就不可用了——单点故障。而且读写都压在一台,查询多了扛不住。

主从复制解决这个——把主库的写操作同步到从库,从库可以分担读、可以做备份、主库挂了还能顶上。本篇讲复制原理、怎么搭主从、复制延迟怎么处理。高可用(自动切换)下一篇讲。

一、复制怎么工作

主库的每次数据变更都记到 binlog。从库去主库拉 binlog,在自己身上回放一遍,数据就和主库一致了:

text
主库写入 → binlog → 从库 IO 线程拉取 → relay log → SQL 线程回放
对象职责
binlog(主库)记录已提交事务的变更
IO 线程(从库)连主库、拉 binlog、存为 relay log
relay log(从库)binlog 的本地副本
SQL 线程(从库)读 relay log 逐条回放
GTID给每个事务一个全局唯一 ID

GTID vs 传统位点

从库要知道从主库的哪个位置开始拉 binlog。两种方式:

方式怎么定位特点
传统binlog 文件名 + 位置(position)手工维护,切换要记位点
GTID全局事务 ID自动协商,新环境优先用 GTID

GTID 给每个事务一个全局唯一 ID,从库自动知道从哪开始、哪些回放过了。切换主从时不用手工算位点,省心。8.0+ 推荐 GTID。

复制是异步的

默认复制是异步的——主库提交后不等从库确认。主库宕机时,最后一部分事务可能还没传到从库,会丢。

半同步复制要求至少一个从库确认收到才返回成功,降低丢失概率,但增加提交延迟。核心数据用半同步,日志类用异步。

二、主库配置

主库要开 binlog 和 GTID:

ini
[mysqld]
server_id=1                                  # 复制拓扑内唯一
log_bin=/data/mysql/logs/mysql-bin
binlog_format=ROW
gtid_mode=ON
enforce_gtid_consistency=ON

server_id 在整个复制拓扑里必须唯一——主从不能一样。改完重启。确认生效:

sql
SHOW VARIABLES LIKE 'gtid_mode';
SHOW VARIABLES LIKE 'server_id';

三、复制账号

从库要连主库拉 binlog,得有专门账号:

sql
CREATE USER 'repl'@'10.0.%' IDENTIFIED BY 'ReplPassword_123!';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'10.0.%';

REPLICATION SLAVE 权限让从库能拉 binlog。限制来源 10.0.%——只让内网从库连。

四、从库初始化(含 Clone 插件)

从库要有一份和主库一致的数据起点,不能从空库开始复制(会缺历史数据)。初始化从库有几种方式:

方式一:Clone 插件(8.0.17+,推荐)

MySQL 自带的 Clone 插件,直接从主库克隆数据到从库,比 mysqldump/XtraBackup 快:

sql
-- 从库上装插件
INSTALL PLUGIN clone SONAME 'mysql_clone.so';

-- 配置克隆来源(指向主库)
CLONE INSTANCE FROM 'repl'@'10.0.0.1':3306
  IDENTIFIED BY 'ReplPassword_123!';

Clone 直接拷贝主库的数据文件到从库,几 GB 到几十 GB 的库几分钟搞定。拷完自动接上复制(GTID 自动同步)。这是 8.0+ 搭从库最快的方式。

限制:只拷 InnoDB 表;拷贝过程中主库的 DDL 会被阻塞。

方式二:mysqldump(小库)

bash
# 主库上导出,带 GTID
mysqldump -uroot -p --all-databases --single-transaction \
  --master-data=2 --set-gtid-purged=ON > full.sql

# 导入从库
mysql -uroot -p < full.sql

适合几十 GB 以内的小库。大库导入慢。

方式三:XtraBackup

物理备份恢复到从库,适合大库。比 mysqldump 快,但要手动处理 GTID 位点衔接,比 Clone 步骤多。

五、从库启动复制

数据初始化好(Clone 会自动配好,dump/XtraBackup 要手动),启动复制:

sql
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST='10.0.0.1',
  SOURCE_USER='repl',
  SOURCE_PASSWORD='ReplPassword_123!',
  SOURCE_AUTO_POSITION=1;     -- GTID 自动定位

START REPLICA;     -- 8.0+ 用 REPLICA(旧版 START SLAVE)

SOURCE_AUTO_POSITION=1 让从库用 GTID 自动定位从哪开始。看复制状态:

sql
SHOW REPLICA STATUS\G

关注两个关键:

  • Replica_IO_Running: Yes — IO 线程在拉 binlog
  • Replica_SQL_Running: Yes — SQL 线程在回放

两个都 Yes 才正常。哪个 No 看下面的 Last_IO_ErrorLast_SQL_Error 找原因。

六、复制延迟

从库回放比主库写入慢,就会有延迟——主库刚写的数据,从库还没回放到。

sql
-- 看延迟多少秒
SHOW REPLICA STATUS\G
-- 关注 Seconds_Behind_Master

延迟的原因和处理:

原因处理
从库机器比主库差升级从库配置
大事务(一条 SQL 改百万行)应用拆小批
从库被慢查询占用回放线程从库别跑重查询
网络带宽不够升级网络

从库延迟是生产常见问题——读写分离时,刚写入的数据从库查不到,用户会觉得"提交了但没生效"。对一致性敏感的查询走主库,不敏感的走从库。

七、只读保护

从库设为只读,防止误写:

sql
SET GLOBAL super_read_only = ON;

从库一旦被写,和主库数据就不一致了,复制可能报错。设只读后,除了复制线程回放,其他写都拒绝。

八、复制异常排查

SHOW REPLICA STATUS 里 IO 或 SQL 线程 No 时:

sql
SHOW REPLICA STATUS\G
-- 看 Last_IO_Error / Last_SQL_Error

常见:

  • IO 线程 No:网络不通、复制账号密码错、主库 binlog 被清理了(从库要的位点没了)
  • SQL 线程 No:回放时冲突(从库已有这行数据、主键冲突)、表结构不一致

binlog 被清理是常见坑——主库 binlog_expire_logs_seconds 设太短,从库延迟太久没拉到的 binlog 被主库删了,从库追不上。生产 binlog 保留时间要够长(至少几天)。

跳过一条出错的事务(确认能跳过时):

sql
STOP REPLICA;
SET GTID_NEXT='uuid:事务号';
BEGIN; COMMIT;     -- 注入一个空事务占位
SET GTID_NEXT='AUTOMATIC';
START REPLICA;

九、延迟从库

故意让从库比主库慢一段时间(比如 1 小时),作为误删数据的兜底——主库误删了,延迟从库还有 1 小时前的数据:

sql
CHANGE REPLICATION SOURCE TO SOURCE_DELAY=3600;

延迟从库不能当实时读用,但误删恢复时是救命稻草。

复制之后

主从复制搭好了,数据能同步了。但主库挂了,从库不会自动变成主库——要人工切换或用高可用组件。下一篇讲 InnoDB Cluster——自动故障切换的高可用方案。