Skip to content

MySQL 主从复制

  主从复制是 MySQL 高可用架构的基础,用于实现读写分离、数据备份、故障转移和负载均衡。理解主从复制原理和常见问题,是构建生产级 MySQL 架构的必备技能。


1. 复制原理

1.1 复制架构

┌─────────────────────────────────────────────────────────────────────┐
│                        主从复制架构                                   │
│                                                                     │
│  ┌──────────────────────┐              ┌──────────────────────┐      │
│  │       主库 (Master)   │              │      从库 (Slave)    │      │
│  │                      │              │                      │      │
│  │  ┌────────────┐     │              │  ┌────────────┐     │      │
│  │  │ 客户端写入   │     │              │  │ 客户端读取   │     │      │
│  │  └─────┬──────┘     │              │  └─────▲──────┘     │      │
│  │        │            │              │        │            │      │
│  │        ▼            │              │        │            │      │
│  │  ┌────────────┐     │              │  ┌─────┴──────┐     │      │
│  │  │  数据变更   │     │              │  │  数据读取   │     │      │
│  │  └─────┬──────┘     │              │  └─────▲──────┘     │      │
│  │        │            │              │        │            │      │
│  │        ▼            │              │        │            │      │
│  │  ┌────────────┐     │              │  ┌─────┴──────┐     │      │
│  │  │   binlog   │     │              │  │  Relay Log │     │      │
│  │  └─────┬──────┘     │              │  └─────▲──────┘     │      │
│  │        │            │              │        │            │      │
│  │  ┌─────┴──────┐     │              │  ┌─────┴──────┐     │      │
│  │  │Binlog Dump │─────┼───网络传输────┼─▶│  IO Thread │     │      │
│  │  │   Thread   │     │              │  └────────────┘     │      │
│  │  └────────────┘     │              │        │            │      │
│  │                     │              │  ┌─────┴──────┐     │      │
│  │                     │              │  │ SQL Thread │     │      │
│  │                     │              │  └────────────┘     │      │
│  └──────────────────────┘              └──────────────────────┘      │
└─────────────────────────────────────────────────────────────────────┘

1.2 三个核心线程

主库线程

线程说明
Binlog Dump Thread主库为每个从库连接创建一个线程,负责将 binlog 发送给从库

从库线程

线程说明
IO Thread从主库读取 binlog,写入本地 relay log(中继日志)
SQL Thread从 relay log 中读取并执行 SQL 语句,将数据变更应用到从库

1.3 复制流程

1. 从库执行 CHANGE MASTER TO 命令,连接到主库。
2. 从库 IO Thread 向主库发送 binlog 复制请求。
3. 主库 Binlog Dump Thread 读取 binlog 并发送给从库。
4. 从库 IO Thread 接收 binlog,写入 relay log。
5. 从库 SQL Thread 读取 relay log 中的事件,回放执行。

详细步骤:
┌──────────────────────────────────────────────────────────────┐
│ 主库                                                         │
│   1. 客户端提交事务                                           │
│   2. 写入 binlog                                             │
│   3. Binlog Dump Thread 读取 binlog                          │
│   4. 发送 binlog event 给从库                                 │
│                                                              │
│ 从库                                                         │
│   5. IO Thread 接收 binlog event                             │
│   6. 写入 relay log                                          │
│   7. SQL Thread 读取 relay log                               │
│   8. 回放执行 SQL(在从库上重新执行)                           │
└──────────────────────────────────────────────────────────────┘

2. 复制模式

2.1 异步复制(Asynchronous)

  MySQL 默认的复制模式。主库执行完事务后立即返回,不等待从库确认。

主库提交事务 ──→ 写入 binlog ──→ 返回客户端成功

                                    └──→ 异步发送给从库(不等待)

优点:主库性能不受从库影响。 缺点:主库宕机时,可能丢失尚未同步到从库的数据。

2.2 半同步复制(Semi-Synchronous)

  主库提交事务后,等待至少一个从库确认收到 binlog 后才返回客户端成功。

主库提交事务 ──→ 写入 binlog ──→ 等待从库 ACK ──→ 返回客户端成功

                                    └──→ 从库收到 binlog,写入 relay log,回复 ACK

配置

sql
-- 主库
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = ON;
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 = ON;

-- 重启 IO Thread 使配置生效
STOP SLAVE IO_THREAD;
START SLAVE IO_THREAD;

优点:减少数据丢失风险。 缺点:主库提交延迟增加,从库响应慢会影响主库吞吐量。

2.3 全同步复制(Synchronous)

  MySQL 原生不支持全同步复制,需要借助第三方工具(如 Galera Cluster、MySQL Group Replication)。

主库提交事务 ──→ 写入 binlog ──→ 等待所有从库执行完成 ──→ 返回客户端成功

优点:数据零丢失。 缺点:性能最差,任意从库故障都会阻塞主库。

2.4 三种模式对比

特性异步复制半同步复制全同步复制
数据丢失风险可能丢失几乎不丢失零丢失
主库性能影响无影响轻微影响显著影响
从库故障影响不影响超时后降级主库阻塞
适用场景性能优先高可靠要求极致一致性

3. 复制格式

3.1 基于语句的复制(SBR)

优点缺点
日志量小某些函数导致主从不一致(NOW()、UUID()、RAND())
便于审计触发器、存储过程可能导致意外结果

3.2 基于行的复制(RBR)

优点缺点
数据一致性好日志量大(批量更新尤为明显)
不会出现主从不一致无法直接查看执行的 SQL

3.3 混合模式(MBR)

MySQL 自动选择 SBR 或 RBR,默认使用 SBR,遇到可能导致不一致的操作时自动切换为 RBR。

sql
-- 查看和设置复制格式
SHOW VARIABLES LIKE 'binlog_format';
SET GLOBAL binlog_format = 'ROW';

4. 主从延迟

4.1 延迟产生的原因

  1. 从库硬件性能差:CPU、内存、磁盘性能低于主库。
  2. 从库负载高:从库承担了大量读请求,SQL Thread 得不到足够资源。
  3. 大事务:主库大事务执行时间短,从库回放时间长。
  4. 从库单线程回放:MySQL 5.6 及之前,从库 SQL Thread 是单线程。
  5. 网络延迟:主从之间网络带宽不足或延迟高。
  6. 锁冲突:从库回放时遇到锁等待。

4.2 延迟监控

sql
-- 查看从库状态
SHOW SLAVE STATUS\G

-- 关键字段
-- Seconds_Behind_Master:主从延迟秒数(0 表示无延迟,NULL 表示异常)
-- Slave_IO_Running:IO 线程是否运行
-- Slave_SQL_Running:SQL 线程是否运行
-- Master_Log_File / Read_Master_Log_Pos:主库 binlog 位置
-- Relay_Master_Log_File / Exec_Master_Log_Pos:从库执行到的位置

-- 计算延迟(更精确)
-- 主库:SELECT @@timestamp;
-- 从库:记录当前时间,查询 SHOW SLAVE STATUS,比较

4.3 延迟解决方案

方案一:并行复制(MySQL 5.6+)

sql
-- MySQL 5.6:基于库的并行复制
SET GLOBAL slave_parallel_workers = 4;

-- MySQL 5.7:基于组提交的并行复制(LOGICAL_CLOCK)
SET GLOBAL slave_parallel_type = LOGICAL_CLOCK;
SET GLOBAL slave_parallel_workers = 8;

-- MySQL 8.0:基于写集合的并行复制(WRITESET)
SET GLOBAL slave_parallel_type = LOGICAL_CLOCK;
SET GLOBAL slave_parallel_workers = 8;
SET GLOBAL binlog_transaction_dependency_tracking = WRITESET;

方案二:提高从库配置

  • 从库硬件配置不低于主库。
  • 将 binlog 和 relay log 放在 SSD 上。
  • 设置 sync_relay_log = 0(降低 relay log 刷盘频率)。

方案三:拆分大事务

sql
-- 不推荐:一次性删除
DELETE FROM logs WHERE create_time < '2023-01-01';

-- 推荐:分批删除
DELETE FROM logs WHERE create_time < '2023-01-01' LIMIT 1000;

方案四:读写分离策略

  • 核心业务读主库,非核心业务读从库。
  • 刚写入的数据读主库(避免因延迟读不到最新数据)。
java
// 使用 Hint 路由到主库
@Transactional
public void createOrder() {
    // 写主库
    orderDao.insert(order);
    // 读主库(刚插入的数据,避免延迟)
    Order newOrder = orderDao.selectById(order.getId());
}

5. GTID 复制

5.1 什么是 GTID

  GTID(Global Transaction Identifier,全局事务标识符)是 MySQL 5.6 引入的全局唯一事务 ID,用于简化主从切换和故障恢复。

GTID 格式

GTID = server_uuid:transaction_id
例如:3E11FA47-71CA-11E1-9E33-C80AA9429562:1-100

优势

  • 不需要手动指定 binlog 文件和位置。
  • 主从切换更简单,自动找到同步点。
  • 支持多主复制(Multi-Source Replication)。

5.2 GTID 配置

sql
-- 主库和从库都需要配置
-- my.cnf:
-- [mysqld]
-- gtid_mode = ON
-- enforce_gtid_consistency = ON
-- log_slave_updates = ON

-- 查看 GTID 状态
SHOW VARIABLES LIKE 'gtid_mode';
SHOW VARIABLES LIKE 'enforce_gtid_consistency';

-- 查看 GTID 执行情况
SHOW MASTER STATUS\G
-- Executed_Gtid_Set:已执行的 GTID 集合

SHOW SLAVE STATUS\G
-- Retrieved_Gtid_Set:已接收的 GTID 集合
-- Executed_Gtid_Set:已执行的 GTID 集合

5.3 基于 GTID 的从库配置

sql
-- 传统方式
CHANGE MASTER TO
    MASTER_HOST = '192.168.1.1',
    MASTER_PORT = 3306,
    MASTER_USER = 'repl',
    MASTER_PASSWORD = 'password',
    MASTER_LOG_FILE = 'mysql-bin.000001',
    MASTER_LOG_POS = 100;

-- GTID 方式(不需要指定日志文件和位置)
CHANGE MASTER TO
    MASTER_HOST = '192.168.1.1',
    MASTER_PORT = 3306,
    MASTER_USER = 'repl',
    MASTER_PASSWORD = 'password',
    MASTER_AUTO_POSITION = 1;  -- 自动定位

5.4 GTID 运维操作

sql
-- 跳过 GTID 事务(当从库回放错误时)
-- 方法一:注入空事务
SET GTID_NEXT = '3E11FA47-71CA-11E1-9E33-C80AA9429562:100';
BEGIN; COMMIT;
SET GTID_NEXT = 'AUTOMATIC';

-- 方法二:使用 mysqlslave skip(MySQL 8.0.26+)
-- STOP SLAVE SQL_THREAD;
-- SET GLOBAL sql_slave_skip_counter = 1;
-- START SLAVE SQL_THREAD;

-- 主从切换后,新主库可能需要重置 GTID
-- RESET MASTER;  -- 清除所有 binlog 和 GTID(谨慎使用)

6. 并行复制

6.1 MySQL 5.6 并行复制

  基于库(Schema)级别的并行复制,不同库的事务可以并行回放。

sql
-- 配置
SET GLOBAL slave_parallel_workers = 4;

限制:同一库的事务仍然串行执行,对单库场景无效。

6.2 MySQL 5.7 并行复制

  基于组提交(Group Commit)的并行复制,同一组提交的事务可以并行回放。

sql
-- 配置
SET GLOBAL slave_parallel_type = LOGICAL_CLOCK;
SET GLOBAL slave_parallel_workers = 8;

原理

  • 主库上在同一组提交的事务之间没有冲突,可以安全地并行回放。
  • 通过 last_committedsequence_number 判断事务的依赖关系。

6.3 MySQL 8.0 并行复制

  基于写集合(Writeset)的并行复制,更细粒度地判断事务依赖。

sql
-- 配置
SET GLOBAL slave_parallel_type = LOGICAL_CLOCK;
SET GLOBAL slave_parallel_workers = 8;
SET GLOBAL binlog_transaction_dependency_tracking = WRITESET;
SET GLOBAL transaction_write_set_extraction = XXHASH64;

原理

  • 提取每个事务修改的主键和唯一键,形成写集合。
  • 写集合不冲突的事务可以并行回放。
  • 并行度高于 5.7 的组提交模式。

6.4 并行复制对比

版本并行维度并行度适用场景
5.6库级多库场景
5.7组提交通用场景
8.0写集合通用场景

7. 读写分离架构

7.1 架构模式

┌───────────────────────────────────────────────────────────────┐
│                      读写分离架构                              │
│                                                               │
│  ┌──────────┐                                                 │
│  │  应用程序  │                                                 │
│  └─────┬─────┘                                                │
│        │                                                      │
│        ▼                                                      │
│  ┌──────────────┐                                             │
│  │ 数据库中间件   │ (ShardingSphere、ProxySQL、Mycat 等)       │
│  │  / 路由层    │                                              │
│  └──┬──────┬────┘                                             │
│     │      │                                                  │
│     ▼      ▼                                                  │
│  ┌──────┐ ┌──────┐ ┌──────┐                                  │
│  │ 写库  │ │ 读库1 │ │ 读库2 │                                 │
│  │(Master)│ │(Slave)│ │(Slave)│                                │
│  └──┬───┘ └──┬───┘ └──┬───┘                                  │
│     │        │        │                                       │
│     └────────┴────────┘                                       │
│          复制 (Replication)                                    │
└───────────────────────────────────────────────────────────────┘

7.2 路由策略

策略说明适用场景
基于 Hint代码中指定读主库还是从库简单场景
基于 SQL 解析中间件解析 SQL,SELECT 路由到从库通用场景
基于事务事务内读主库,事务外读从库一致性要求高
基于延迟延迟超过阈值的从库不分配读请求高可用要求

7.3 读写分离问题

主从延迟导致的问题

  • 写后立刻读,可能读不到刚写入的数据。
  • 解决方案:写后读主库、延迟阈值剔除从库。

代码示例

java
@Service
public class UserService {
    
    // 写操作 - 走主库
    @Transactional
    public void createUser(User user) {
        userDao.insert(user);
    }
    
    // 写后读 - 走主库
    @Transactional
    public User getUserAfterWrite(Long id) {
        User user = userDao.selectById(id);  // 主库
        return user;
    }
    
    // 普通读 - 走从库
    public List<User> listUsers() {
        return userDao.selectAll();  // 从库
    }
}

8. 复制搭建步骤

8.1 主库配置

ini
# my.cnf
[mysqld]
server_id = 1                      # 唯一 ID
log_bin = mysql-bin                # 开启 binlog
binlog_format = ROW                # binlog 格式
sync_binlog = 1                    # binlog 刷盘
innodb_flush_log_at_trx_commit = 1 # redo log 刷盘
gtid_mode = ON                     # 开启 GTID(推荐)
enforce_gtid_consistency = ON
log_slave_updates = ON             # 从库也记录 binlog(级联复制需要)

8.2 创建复制用户

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

8.3 从库配置

ini
# my.cnf
[mysqld]
server_id = 2                      # 唯一 ID,不能与主库相同
log_bin = mysql-bin                # 开启 binlog(级联复制或故障切换需要)
binlog_format = ROW
relay_log = mysql-relay-bin        # relay log
gtid_mode = ON
enforce_gtid_consistency = ON
log_slave_updates = ON
read_only = ON                     # 从库只读

8.4 初始化从库数据

bash
# 方法一:mysqldump 备份恢复
mysqldump -u root -p --master-data=2 --single-transaction --all-databases > backup.sql
mysql -u root -p < backup.sql

# 方法二:xtrabackup(推荐,支持热备)
xtrabackup --backup --target-dir=/backup
xtrabackup --prepare --target-dir=/backup
xtrabackup --copy-back --target-dir=/backup

8.5 启动复制

sql
-- 从库上执行
-- 传统方式
CHANGE MASTER TO
    MASTER_HOST = '192.168.1.1',
    MASTER_PORT = 3306,
    MASTER_USER = 'repl',
    MASTER_PASSWORD = 'password',
    MASTER_LOG_FILE = 'mysql-bin.000001',
    MASTER_LOG_POS = 100;

-- GTID 方式
CHANGE MASTER TO
    MASTER_HOST = '192.168.1.1',
    MASTER_PORT = 3306,
    MASTER_USER = 'repl',
    MASTER_PASSWORD = 'password',
    MASTER_AUTO_POSITION = 1;

-- 启动复制
START SLAVE;
-- 或 MySQL 8.0.22+
START REPLICA;

-- 查看状态
SHOW SLAVE STATUS\G

9. 常见复制问题与排查

9.1 主从数据不一致

原因

  • 从库上直接执行了写操作。
  • 使用了 SBR 且包含不确定函数。
  • 从库回放时遇到错误被跳过。

排查

bash
# 使用 pt-table-checksum 检查数据一致性
pt-table-checksum --replicate=percona.checksums h=127.0.0.1

# 使用 pt-table-sync 修复不一致
pt-table-sync --print --sync-to-master h=slave_host,D=test,t=users

9.2 SQL 线程报错

sql
-- 查看错误
SHOW SLAVE STATUS\G
-- Last_SQL_Error, Last_SQL_Error_Timestamp

-- 常见错误及处理
-- 1. 主键冲突:可能是从库有额外数据
--    跳过错误(谨慎)
SET GLOBAL sql_slave_skip_counter = 1;
START SLAVE;

-- 2. 表不存在:可能是从库缺少表
--    从主库导出表结构到从库

-- 3. 1062 (Duplicate entry):主键冲突
--    删除从库冲突数据后重试,或跳过

9.3 IO 线程连接失败

sql
-- 检查网络连通性
-- ping master_host

-- 检查防火墙
-- 确保 3306 端口开放

-- 检查复制用户权限
SHOW GRANTS FOR 'repl'@'%';

-- 检查 server_id 是否冲突
SHOW VARIABLES LIKE 'server_id';

9.4 复制延迟排查

sql
-- 1. 查看主从延迟
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master

-- 2. 检查从库是否有长事务
SELECT * FROM information_schema.INNODB_TRX 
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;

-- 3. 检查从库锁等待
SELECT * FROM sys.innodb_lock_waits;

-- 4. 检查从库磁盘 IO
-- iostat -x 1

10. 高可用复制架构

10.1 一主一从

Master ──→ Slave

最简单的架构,适合小型项目或开发环境。

10.2 一主多从

         ┌──→ Slave1
Master ──┼──→ Slave2
         └──→ Slave3

读写分离,多个从库分担读压力。

10.3 级联复制

Master ──→ Slave1 ──→ Slave2

减少主库的复制压力,Slave1 同时是主库和从库。

10.4 双主复制

Master1 ←──→ Master2

互为主从,适合高可用架构,但需要处理主键冲突。

10.5 MHA 架构

┌──────────────────────────────┐
│           MHA Manager        │
│  (监控、故障检测、自动切换)    │
└──────┬──────┬──────┬─────────┘
       │      │      │
       ▼      ▼      ▼
    Master  Slave1  Slave2

MHA(Master High Availability)是成熟的高可用方案,支持自动故障转移。

10.6 MySQL Group Replication

┌──────────────────────────────────────┐
│          MySQL Group Replication     │
│                                      │
│    Node1 ←──→ Node2 ←──→ Node3      │
│    (主)       (从)       (从)        │
│                                      │
│    基于 Paxos 协议的强一致性复制       │
└──────────────────────────────────────┘

MySQL 5.7.17+ 原生支持,MySQL 8.0 成熟度更高。


11. 生产环境最佳实践

  1. 复制格式使用 ROW:避免主从不一致问题。
  2. 开启 GTID:简化主从切换和故障恢复。
  3. 使用半同步复制:减少数据丢失风险。
  4. 从库开启只读read_only = ON,防止误操作。
  5. 监控复制延迟:设置告警阈值(如 10 秒)。
  6. 定期检查一致性:使用 pt-table-checksum。
  7. 主从配置一致:MySQL 版本、参数配置尽量一致。
  8. 备份策略:在从库上执行备份,减少对主库影响。

12. 总结

  • 主从复制通过三个线程(Binlog Dump、IO、SQL)实现数据同步。
  • 复制模式分为异步、半同步和全同步,异步性能最好,全同步数据最安全。
  • 复制格式推荐 ROW,保证数据一致性。
  • 主从延迟是常见问题,可通过并行复制、优化硬件、拆分大事务等方式缓解。
  • GTID 简化了复制管理和故障切换。
  • 读写分离提高系统吞吐量,但需注意主从延迟导致的读不到最新数据问题。
  • 高可用架构从一主一从到 MGR 逐步演进,根据业务需求选择。