← 返回 MYSQL 列表

主从复制

主从复制

一、主从复制架构

1.1 基本架构

应用 → 主库(Master)
           │
           │  Binlog
           │
           ▼
      从库(Slave)
    ┌──────┴──────┐
    │             │
  中继日志    数据文件
  (Relay Log)

主从复制的价值

  • 读写分离:主库写、从库读,分担压力
  • 数据备份:从库可用于备份,不影响主库
  • 高可用:主库故障时将从库提升为主库
  • 数据分析:从库用于报表查询、数据分析

1.2 复制架构类型

一主一从:最基本的架构
  主库 → 从库

一主多从:读写分离
  主库 → 从库1(读)
  主库 → 从库2(读)
  主库 → 从库3(备份)

主主复制:双向复制(较少用)
  主库A ↔ 主库B

多级复制:减少主库压力
  主库 → 从库1 → 从库2

级联复制:负载均衡
  主库 → 从库A
       → 从库B → 从库C

二、复制原理

2.1 三线程工作流程

主库:
  Binlog Dump Thread:发送 binlog 给从库

从库:
  IO Thread:接收主库 binlog,写入 relay log
  SQL Thread:读取 relay log,执行 SQL

流程:
  1. 从库 IO Thread 连接主库
  2. 主库 Binlog Dump Thread 发送 binlog
  3. 从库 IO Thread 接收并写入 relay log
  4. 从库 SQL Thread 读取 relay log 并执行
  5. 从库数据与主库保持同步

2.2 复制配置

主库配置(my.cnf)

ini
[mysqld]
server_id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW
binlog_do_db = mydb        # 只复制指定库
# binlog_ignore_db = test   # 忽略指定库
expire_logs_days = 7
max_binlog_size = 500M

从库配置(my.cnf)

ini
[mysqld]
server_id = 2              # 必须与主库不同
log_bin = /var/log/mysql/mysql-bin.log
relay_log = /var/log/mysql/relay-bin.log
read_only = 1              # 从库设置为只读(防止误写)
log_slave_updates = 1      # 记录从库更新的 binlog(级联复制需要)
skip_slave_start = 1       # 不自动启动复制(手动控制)

2.3 搭建主从复制

sql
-- 步骤1:主库创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'repl_password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;

-- 步骤2:主库锁定表并查看 binlog 位置
FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS;
-- File: mysql-bin.000001, Position: 12345

-- 步骤3:从另一个终端导出主库数据
mysqldump -u root -p --all-databases --master-data=2 > master_data.sql

-- 步骤4:主库解锁
UNLOCK TABLES;

-- 步骤5:从库导入数据
mysql -u root -p < master_data.sql

-- 步骤6:从库配置复制
CHANGE MASTER TO
    MASTER_HOST = '192.168.1.100',
    MASTER_PORT = 3306,
    MASTER_USER = 'repl',
    MASTER_PASSWORD = 'repl_password',
    MASTER_LOG_FILE = 'mysql-bin.000001',
    MASTER_LOG_POS = 12345;

-- 步骤7:启动复制
START SLAVE;

-- 步骤8:检查复制状态
SHOW SLAVE STATUS\G
-- Slave_IO_Running: Yes
-- Slave_SQL_Running: Yes
-- Seconds_Behind_Master: 0

2.4 GTID 复制(MySQL 5.6+)

GTID(Global Transaction Identifier)全局事务 ID,简化了主从复制配置和故障切换。

ini
[mysqld]
gtid_mode = ON
enforce_gtid_consistency = ON
log_slave_updates = ON
sql
-- GTID 模式下的 CHANGE MASTER
CHANGE MASTER TO
    MASTER_HOST = '192.168.1.100',
    MASTER_PORT = 3306,
    MASTER_USER = 'repl',
    MASTER_PASSWORD = 'repl_password',
    MASTER_AUTO_POSITION = 1;  -- 自动定位,无需指定文件位置

-- 查看 GTID 执行情况
SHOW MASTER STATUS\G
-- Executed_Gtid_Set: cc3b3c3b-...

三、主从同步延迟

3.1 延迟原因

sql
-- 查看从库延迟
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master: 120  ← 延迟 120 秒
原因 说明
从库写入慢 从库单线程 SQL Thread 执行落后于主库并发写入
主库大事务 一个大事务在主库执行完,在从库也要执行同样长时间
从库硬件差 从库配置低于主库
从库在读 从库大量读查询抢占资源,影响 SQL Thread 执行
网络延迟 主从之间网络不稳定或带宽不足

3.2 延迟解决方案

sql
-- 1. 从库并行复制(MySQL 5.7+)

-- 设置并行复制策略
-- slave_parallel_workers:并行线程数
-- slave_parallel_type:并行类型

-- my.cnf 从库配置
[mysqld]
slave_parallel_workers = 4           -- 4 个并行线程
slave_parallel_type = LOGICAL_CLOCK  -- 基于组提交的并行复制

-- 2. 避免大事务

-- 坏:一个大事务删除大量数据
DELETE FROM orders WHERE create_time < '2023-01-01';
-- 好:分批删除
DELETE FROM orders WHERE create_time < '2023-01-01' LIMIT 1000;

-- 3. 升级从库硬件

-- 4. 使用多级复制
-- 主库 → 从库A(同步复制)
--        → 从库B(异步复制,延迟可以接受)

-- 5. 半同步复制(保证数据不丢但减少延迟风险)
-- 安装插件(主库和从库都需要)
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';

-- 启用
SET GLOBAL rpl_semi_sync_master_enabled = 1;
SET GLOBAL rpl_semi_sync_slave_enabled = 1;

3.3 延迟监控与告警

sql
-- 检查延迟
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master: N

-- 检查 relay log 积压
SHOW SLAVE STATUS\G
-- Relay_Log_Space: 1048576  -- 1GB,如果持续增长说明 SQL Thread 卡住了

-- 监控脚本
SELECT
    VARIABLE_VALUE AS seconds_behind_master
FROM performance_schema.status_by_thread
WHERE VARIABLE_NAME = 'seconds_behind_master'
  AND THREAD_ID IN (
      SELECT THREAD_ID
      FROM performance_schema.threads
      WHERE PROCESSLIST_ID = (
          SELECT ID FROM information_schema.PROCESSLIST
          WHERE USER = 'system user'
          AND STATE LIKE 'Waiting for master to send event'
      )
  );

3.4 数据不一致处理

sql
-- 场景:主从数据不一致

-- 检查是否有不一致错误
SHOW SLAVE STATUS\G
-- Last_SQL_Error: ...

-- 方案1:跳过错误(紧急处理)

-- 跳过单条错误
SET GLOBAL sql_slave_skip_counter = 1;
START SLAVE;

-- 方案2:使用 pt-table-checksum 检查一致性
pt-table-checksum --host=主库IP --user=root --password=xxx

-- 方案3:使用 pt-table-sync 修复
pt-table-sync --execute \
    --sync-to-master h=从库IP,u=root,p=xxx

-- 方案4:重新搭建从库(最彻底)
-- 1. 停止从库
STOP SLAVE;

-- 2. 重新从主库导出导入
mysqldump -u root -p --all-databases --master-data=2 > master_data.sql
mysql -u root -p < master_data.sql

-- 3. 重新配置并启动
CHANGE MASTER TO ...;
START SLAVE;

四、实战案例

案例1:一主两从 + 读写分离

sql
-- 主库配置(192.168.1.100)
-- my.cnf 见上文

-- 从库1(192.168.1.101):用于线上读
-- 从库2(192.168.1.102):用于报表统计

-- 应用层读写分离(Java + ShardingSphere)
# application.yml
spring:
  shardingsphere:
    datasource:
      names: master,slave1,slave2
      master:
        url: jdbc:mysql://192.168.1.100:3306/mydb
      slave1:
        url: jdbc:mysql://192.168.1.101:3306/mydb
      slave2:
        url: jdbc:mysql://192.168.1.102:3306/mydb
    masterslave:
      name: ms
      master-data-source-name: master
      slave-data-source-names: slave1,slave2
      load-balance-algorithm-type: round_robin

案例2:主从切换(手动)

sql
-- 场景:主库宕机,将从库提升为主库

-- 步骤1:确认从库已经应用完所有 relay log
STOP SLAVE IO_THREAD;
SHOW PROCESSLIST;
-- 等待 SQL Thread 执行完毕
SHOW SLAVE STATUS\G
-- Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates

-- 步骤2:停止复制
STOP SLAVE;
RESET SLAVE ALL;

-- 步骤3:启用写入
SET GLOBAL read_only = OFF;

-- 步骤4:应用切换(修改连接配置)
-- 更新应用的数据库连接指向新的主库

-- 步骤5:修复原主库(如果还能恢复)
-- 原主库恢复后,作为新主库的从库重新搭建

案例3:GTID 复制故障恢复

sql
-- 查看 GTID 执行情况
SHOW MASTER STATUS\G
-- 或
SELECT @@GLOBAL.gtid_executed;

-- 从库 GTID 与主库不一致时
-- 跳过指定 GTID
SET GTID_NEXT = 'cc3b3c3b-...:12345';
BEGIN;
COMMIT;
SET GTID_NEXT = 'AUTOMATIC';

-- 重置 GTID(谨慎使用,会丢失所有 GTID 信息)
RESET MASTER;

-- 主库和从库 GTID 集合
-- 主库:cc3b3c3b-...:1-1000
-- 从库:cc3b3c3b-...:1-900

-- 如果从库缺失某些 GTID,会自动从主库拉取
CHANGE MASTER TO MASTER_AUTO_POSITION = 1;
START SLAVE;

五、常见问题与排查

5.1 复制中断

sql
-- 问题1:IO Thread 无法连接
-- Last_IO_Error: error connecting to master

-- 排查:
-- 1. 网络是否能通
ping 192.168.1.100

-- 2. 复制用户权限
SELECT user, host FROM mysql.user;

-- 3. 防火墙是否放行 3306 端口

-- 4. 主库 bind-address 配置
SHOW VARIABLES LIKE 'bind_address';

-- 问题2:SQL Thread 执行失败
-- Last_SQL_Error: ...

-- 常见错误:
-- 1062:主键冲突(从库已有相同主键)
-- 1032:记录不存在(从库缺少要更新的行)

-- 解决方案:
-- 1. 跳过错误(如果数据可以不一致)
SET GLOBAL sql_slave_skip_counter = 1;
START SLAVE;

-- 2. 手动修复数据,然后重启复制
-- 插入缺少的数据,或删除冲突的数据

-- 3. 重新搭建从库(最稳妥)

5.2 复制延迟排查

sql
-- 查看 relay log 积压
SHOW SLAVE STATUS\G
-- Relay_Log_Space: 2147483648  -- 2GB,SQL Thread 处理不过来

-- 查看 SQL Thread 当前正在执行的语句
SHOW PROCESSLIST;
-- State: System lock / updating / ...

-- 检查从库是否有大查询占用资源
SHOW FULL PROCESSLIST;
-- 如果有大量 SELECT 正在执行,考虑限制查询或加从库

-- 查看 InnoDB 引擎状态
SHOW ENGINE INNODB STATUS\G
-- 关注 History list length(过长表示 purge 跟不上)

总结

知识点 核心要点
三线程 主库 Dump Thread → 从库 IO Thread(写relay log) → SQL Thread(执行)
搭建步骤 创建用户 → 导出数据 → CHANGE MASTER → START SLAVE
延迟原因 从库单线程、大事务、硬件差异、查询压力
延迟解决 并行复制、避免大事务、半同步、升级硬件
数据不一致 pt-table-checksum 检查、pt-table-sync 修复、重搭从库
GTID 简化复制管理、自动定位、方便故障切换