MySQL 主从复制原理与读写分离实战

主从复制是数据库高可用和扩展性的基础。本文从binlog 原理出发,深入剖析 MySQL 主从复制的工作流程、三种复制方式(异步/半同步/延迟)、GTID 复制、复制延迟原因与处理,以及读写分离的架构设计与实战。

一、主从复制概述

1.1 为什么需要主从复制

主从优势

读写分离

主库写,从库读

提升性能

数据备份

从库实时备份

数据安全

高可用

主库故障时切换从库

服务可用性

单机问题

读写都在同一台 MySQL

性能瓶颈

单点故障

无法水平扩展

1.2 主从复制架构

复制流程

Master: 写数据

生成 binlog 事件

Dump 线程发送 binlog

IO Thread 接收

写入 Relay Log

SQL Thread 执行

Slave 数据更新

主从架构

Master 主库

binlog

Slave 从库1

Slave 从库2

Slave 从库3


二、binlog 详解

2.1 binlog 的作用

binlog三大作用

数据恢复

使用 binlog 恢复数据

主从复制

传输到从库实现同步

增量备份

只记录增量变化

2.2 binlog 三种格式

MIXED 模式

混合模式

STATEMENT 为主

不确定时切换 ROW

MySQL 8.0 默认

ROW 模式

记录数据变化

日志量大

数据绝对一致

推荐使用

STATEMENT 模式

记录 SQL 语句

日志量小

可能导致数据不一致

如 NOW(), UUID()

2.3 binlog 格式对比

-- 表结构
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    create_time DATETIME
);

-- 插入数据
INSERT INTO users VALUES (1, '张三', NOW());

-- STATEMENT 模式 binlog 内容
INSERT INTO users VALUES (1, '张三', NOW())

-- ROW 模式 binlog 内容
### INSERT INTO test.users
### SET
###   @1=1
###   @2='张三'
###   @3='2024-01-01 12:00:00'

2.4 binlog 文件结构

事件类型

QUERY: SQL 语句

TABLE_MAP: 表信息

WRITE/UPDATE/DELETE: 行数据

XID: 事务提交

binlog事件

Format_description_event

Query_event

Table_map_event

Write_rows_event

Xid_event

...

binlog文件

binlog.000001

binlog index

binlog.000002


三、主从复制原理

3.1 复制工作流程

Slave Relay Log SQL Thread IO Thread Dump Thread Master Slave Relay Log SQL Thread IO Thread Dump Thread Master 事务提交 写入 binlog Dump 线程读取 binlog 发送 binlog events 写入 relay-log ACK 确认 读取 relay-log 执行 SQL 数据更新

3.2 核心线程

Slave线程

Master线程

binlog

relay-log

Master

Binlog Dump Thread

读取 binlog

发送给 Slave

Slave

IO Thread

连接 Master

接收 binlog,写入 relay-log

SQL Thread

读取 relay-log

执行 SQL

3.3 复制配置

# Master 配置
[mysqld]
server-id = 1
log-bin = /var/lib/mysql/mysql-bin
binlog-format = ROW
sync-binlog = 1
max-binlog-size = 100M

# Slave 配置
[mysqld]
server-id = 2
relay-log = /var/lib/mysql/relay-log
relay-log-space-limit = 10G
read-only = ON

3.4 配置主从复制

-- Master: 创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

-- Master: 获取 binlog 位置
SHOW MASTER STATUS;
-- +------------------+----------+
-- | File             | Position |
-- +------------------+----------+
-- | mysql-bin.000001 | 154      |
-- +------------------+----------+

-- Slave: 配置主从复制
CHANGE MASTER TO
    MASTER_HOST = 'master-host',
    MASTER_PORT = 3306,
    MASTER_USER = 'repl',
    MASTER_PASSWORD = 'password',
    MASTER_LOG_FILE = 'mysql-bin.000001',
    MASTER_LOG_POS = 154;

-- Slave: 启动复制
START SLAVE;

-- Slave: 查看复制状态
SHOW SLAVE STATUS\G

四、复制方式

4.1 异步复制

特点

主库提交后立即返回

不等待从库确认

性能最好

可能丢失数据

异步复制流程

Master 执行事务

提交事务

写入 binlog

返回客户端

异步发送 binlog

Slave 接收并执行

4.2 半同步复制

特点

至少一个从库写入成功才返回

数据安全性提高

性能略有下降

MySQL 5.7+ 支持

半同步复制流程

Master 执行事务

提交事务

写入 binlog

等待至少一个从库确认

收到 ACK

返回客户端

异步发送到其他从库

4.3 配置半同步复制

-- Master: 安装半同步插件
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;  -- 等待超时(ms)

-- Slave: 安装半同步插件
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';

SET GLOBAL rpl_semi_sync_slave_enabled = 1;

-- 重启 IO Thread
STOP SLAVE IO_THREAD;
START SLAVE IO_THREAD;

4.4 延迟复制

配置

CHANGE MASTER TO MASTER_DELAY = 3600;

延迟 1 小时复制

单位:秒

延迟复制场景

数据误删恢复

从库延迟 N 小时

主库误删可在从库恢复

测试环境

模拟误操作

测试数据恢复


五、GTID 复制

5.1 GTID 概念

示例

3E11FA47-71CA-11E1-9E33-C80AA9429562:23

UUID: 3E11FA47-71CA-11E1-9E33-C80AA9429562

事务号: 23

GTID结构

GTID = source_id:transaction_id

source_id: 源服务器 UUID

transaction_id: 事务序号

5.2 GTID 工作流程

GTID流程

Master 提交事务

生成 GTID

写入 binlog

发送给 Slave

Slave 记录 GTID

检查 GTID 是否已执行

未执行则应用

GTID优势

自动定位

无需指定 binlog 位置

故障切换简单

一致性保证

事务不会丢失或重复

全局唯一

5.3 GTID 配置

# Master
[mysqld]
gtid-mode = ON
enforce-gtid-consistency = ON
log-slave-updates = ON

# Slave
[mysqld]
gtid-mode = ON
enforce-gtid-consistency = ON
log-slave-updates = ON

5.4 GTID 故障转移

-- 传统方式需要指定 binlog 位置
CHANGE MASTER TO
    MASTER_LOG_FILE = 'mysql-bin.000123',
    MASTER_LOG_POS = 456;

-- GTID 方式更简单
CHANGE MASTER TO
    MASTER_AUTO_POSITION = 1;
-- MySQL 自动计算需要同步的位置

六、复制延迟

6.1 延迟原因

延迟原因

主库并发高

从库跟不上

大事务

binlog 大

传输和应用慢

从库服务器性能差

执行慢

网络延迟

binlog 传输慢

从库压力大

查询竞争资源

6.2 延迟监控

-- 查看复制延迟
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master: 0 表示没有延迟

-- 查看详细状态
SHOW SLAVE STATUS\G
-- Relay_Log_Pos: 应用的 Relay Log 位置
-- Exec_Master_Log_Pos: 执行的 Master binlog 位置
-- Read_Master_Log_Pos: 读取的 Master binlog 位置

-- 延迟原因分析
SHOW STATUS LIKE 'Slave%';

6.3 减少延迟的方法

优化方法

并行复制

MySQL 5.7+ 组提交

多线程应用 Relay Log

架构优化

从库配置 SSD

从库独立部署

业务优化

大事务拆分

减少主库并发

参数调优

slave_parallel_workers

slave_parallel_type

6.4 并行复制配置

-- MySQL 5.7+ 并行复制
SET GLOBAL slave_parallel_workers = 4;  -- 工作线程数
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';  -- 基于组提交

-- MySQL 8.0+
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
SET GLOBAL slave_parallel_workers = 8;

-- 配置文件中设置
[mysqld]
slave_parallel_workers = 8
slave_parallel_type = LOGICAL_CLOCK
slave_preserve_commit_order = ON

六、并行复制深度解析

6.5 并行复制原理

并行度提升

主库并发提交

slave_parallel_workers = N

从库 N 个工作线程

延迟大幅降低

并行复制核心思想

从库多线程并行应用

组提交 (Group Commit)

同一组的事务可以并行

不改变原语义

传统单线程复制的问题

大事务导致复制延迟

SQL Thread 串行执行

主库并发,从库串行

从库严重落后

6.6 三种并行复制策略

WRITESET (8.0+)

按 Writeset 并行

基于主键/唯一键冲突检测

更细粒度的并行

全局唯一 ID 冲突少时效果最好

LOGICAL_CLOCK (5.7+)

按组提交并行

同一组提交的事务可并行

需要 master_info_repository = TABLE

slave_preserve_commit_order = ON

推荐使用

DATABASE (5.6)

按库并行

不同库的事务可并行

简单,粒度粗

问题: 单库多表场景无效

6.7 LOGICAL_CLOCK 源码级解析

约束条件

slave_preserve_commit_order = ON

必须按顺序提交

保证从库数据一致性

开启后必须开启 slave_parallel_workers

Slave 端: 并行应用

Coordinator Thread

分发事务到 Worker

Worker 1: 执行事务A

Worker 2: 执行事务B

Worker 1: 等待提交

Worker 2: 等待提交

按顺序提交

Master 端: 组提交

事务A BEGIN

事务B BEGIN

事务A COMMIT

事务B COMMIT

flush binlog (fsync)

同一组: last_committed

同一组: sequence_number

6.8 WRITESET 并行复制配置

-- MySQL 8.0 配置
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
SET GLOBAL slave_parallel_workers = 16;

-- WRITESET 额外配置
SET GLOBAL transaction_write_set_extraction = 'XXHASH64';
SET GLOBAL slave_parallel_type = 'WRITESET';
SET GLOBAL slave_parallel_workers = 16;

-- 验证配置
SHOW VARIABLES LIKE 'slave_parallel%';
SHOW VARIABLES LIKE 'transaction_write_set_extraction';

-- 监控并行复制状态
SHOW STATUS LIKE 'Slave_parallel_%';
-- Slave_parallel_threads: 活跃线程数
-- Slave_parallel_retries: 重试次数

6.9 并行复制参数调优

性能测试

测试步骤

sysbench 压测主库

观察 Seconds_Behind_Master

调整 parallel_workers 找到最优值

监控指标

关键指标

Slave_heartlog_packets: 心跳包

Slave_received_heartbeats: 收到心跳

Slave_last_heartbeat_timestamp: 最后心跳时间

参数配置

slave_parallel_workers

建议: CPU 核心数的 1/2 ~ 2/3

太多线程: 竞争资源

太少线程: 并行度不够


六·续、主从数据校验与修复

6.10 为什么需要数据校验

校验工具

pt-table-checksum

在线校验,不锁表

计算 CRC32 校验和

报告不一致的表

可能导致数据不一致的场景

从库误操作

手动修改从库数据

复制中断期间的变更

Relay Log 应用失败

硬件故障

从库数据页损坏

人为错误

误删数据

版本升级问题

不同版本行为差异

6.11 pt-table-checksum 使用

# 安装 Percona Toolkit
# apt install percona-toolkit

# 在主库执行校验
pt-table-checksum h=master-host,u=root,p=password \
    --nocheck-replication-filters \
    --replicate=test.checksums \
    --databases=myapp

# 参数说明:
# --replicate: 结果写入哪个表
# --nocheck-replication-filters: 不检查复制过滤器
# --databases: 指定数据库

# 在从库检查差异
pt-table-sync --print h=slave-host,u=root,p=password \
    --replicate=test.checksums \
    --database=myapp

# 直接修复不一致
pt-table-sync --execute h=slave-host,u=root,p=password \
    --replicate=test.checksums

6.12 手动修复不一致数据

-- 方法1: 直接同步
DELETE FROM slave_db.users WHERE id = 123;
INSERT INTO slave_db.users SELECT * FROM master_db.users WHERE id = 123;

-- 方法2: 基于 GTID 跳过错误
STOP SLAVE;
SET gtid_next = 'source_id:transaction_id';
BEGIN;
COMMIT;
SET gtid_next = 'AUTOMATIC';
START SLAVE;

-- 方法3: 重新同步表
-- 从主库导出
mysqldump -h master -u root -p myapp users > users.sql

-- 导入从库
mysql -h slave -u root -p myapp < users.sql

七、读写分离

7.1 读写分离架构

路由规则

数据层

应用层

应用服务

Router/Proxy

Master 写

Slave 读1

Slave 读2

Slave 读3

写 -> Master
读 -> Slave

7.2 读写分离实现方式

服务端代理

MySQL Proxy

Atlas

MaxScale

MySQL Router

客户端路由

应用层实现

ShardingSphere

MySQL Connector

手动路由

7.3 ShardingSphere-JDBC 配置

// Maven 依赖
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-core</artifactId>
    <version>5.3.0</version>
</dependency>

// 配置
spring:
  datasource:
    master:
      jdbc-url: jdbc:mysql://master:3306/db?useSSL=false
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave:
      jdbc-url: jdbc:mysql://slave:3306/db?useSSL=false
      driver-class-name: com.mysql.cj.jdbc.Driver

// 读写分离配置
spring:
  shardingsphere:
    datasource:
      names: master,slave
      master:
        type: com.zaxxer.hikari.HikariDataSource
      slave:
        type: com.zaxxer.hikari.HikariDataSource
    master-slave:
      data-sources:
        ds-master:
          master-data-source-name: master
          slave-data-source-names: slave
          load-balance-algorithm-type: round_robin
      config:
        read-write-split:
          data-sources:
            ds-master:
              type: Static
              props:
                write-data-source-name: master
                read-data-source-names: slave
          load-balancers:
            roundRobin:
              type: ROUND_ROBIN

7.4 手动读写分离

// 数据源选择器
@Component
public class DataSourceSelector {
    
    @Autowired
    @Qualifier("masterDataSource")
    private DataSource masterDataSource;
    
    @Autowired
    @Qualifier("slaveDataSource")
    private DataSource slaveDataSource;
    
    public DataSource getDataSource(boolean isWrite) {
        return isWrite ? masterDataSource : slaveDataSource;
    }
}

// 使用示例
@Service
public class UserService {
    
    @Autowired
    private DataSourceSelector dataSourceSelector;
    
    @Transactional
    public void saveUser(User user) {
        DataSource ds = dataSourceSelector.getDataSource(true);
        // 使用 masterDataSource
    }
    
    public User getUser(Long id) {
        DataSource ds = dataSourceSelector.getDataSource(false);
        // 使用 slaveDataSource
    }
}

7.5 读写分离注意事项

解决方案

强制走主库

关键业务走主库

延迟读取

写入后等待一定时间再读

事务处理

事务内读写都走主库

问题

复制延迟

刚写入的数据立即读取可能不存在

数据一致性

主从数据可能不一致

事务问题

读写都在事务中必须走主库


八、面试高频问题

8.1 MySQL 主从复制原理?

主从复制原理:

1. Master 端
   - 事务提交时,将写操作记录到 binlog
   - Dump 线程读取 binlog,发送给 Slave

2. Slave 端
   - IO Thread: 接收 binlog,写入 relay-log
   - SQL Thread: 读取 relay-log,执行 SQL

3. 复制模式
   - 异步复制: 不等待从库确认
   - 半同步复制: 等待至少一个从库确认
   - 延迟复制: 从库延迟 N 秒复制

8.2 binlog 有哪几种格式?区别?

┌─────────────┬────────────────────────────────┬───────────────────────┐
│   格式        │         记录内容                  │       优缺点           │
├─────────────┼────────────────────────────────┼───────────────────────┤
│ STATEMENT   │ SQL 语句本身                     │ 日志量小,可能不一致   │
│ ROW         │ 数据行的变化                     │ 日志量大,绝对一致     │
│ MIXED       │ 混合模式,默认 STATEMENT         │ 平衡方案               │
└─────────────┴────────────────────────────────┴───────────────────────┘

建议使用 ROW 格式,确保数据一致性。

8.3 如何处理主从复制延迟?

1. 架构层面
   - 从库使用 SSD
   - 从库独立部署,不与应用竞争资源
   - 使用并行复制

2. 参数调优
   - slave_parallel_workers
   - slave_parallel_type = LOGICAL_CLOCK

3. 业务层面
   - 强制关键业务走主库
   - 读写分离架构中,读从库前先判断延迟

4. 监控
   - 监控 Seconds_Behind_Master
   - 设置告警

8.4 什么是 GTID?有什么优势?

GTID = Global Transaction ID = source_id:transaction_id

组成:
- source_id: Master 的 UUID
- transaction_id: 事务序号

优势:
1. 自动定位
   - 不需要指定 binlog 文件和位置
   - CHANGE MASTER TO MASTER_AUTO_POSITION = 1

2. 简化故障转移
   - 新从库不需要知道 binlog 位置
   - 自动同步缺失的事务

3. 一致性保证
   - 每个事务有唯一 ID
   - 不会重复执行或遗漏

8.5 读写分离的常见问题?

1. 复制延迟
   - 刚写入的数据立即读取可能不存在
   - 解决方案: 强制关键业务走主库

2. 数据不一致
   - 主从数据可能短暂不一致
   - 解决方案: 使用半同步复制

3. 事务问题
   - 事务中先写后读必须走主库
   - 解决方案: @Transactional 注解路由到主库

4. 自增主键问题
   - 多主架构需要使用统一 ID 生成器
   - 解决方案: Snowflake 算法

九、总结

9.1 复制方式对比

方式 性能 安全性 配置复杂度
异步复制 简单
半同步复制 中等
延迟复制 简单
GTID 复制 简单

9.2 优化建议

主从复制优化建议:

1. 网络优化
   - 主从网络延迟低
   - 使用千兆网络

2. 硬件优化
   - 从库使用 SSD
   - 从库独立磁盘

3. 参数优化
   - 并行复制
   - 合理设置 buffer

4. 监控
   - 监控复制延迟
   - 监控从库状态

9.3 架构建议

生产环境架构建议:

1. 一主多从
   - 1 个主库
   - 2-3 个从库用于读写分离
   - 1 个从库用于备份

2. 半同步复制
   - 保证数据安全性
   - 配置 wait_timeout

3. 读写分离
   - 读操作分散到从库
   - 写操作走主库
   - 关键业务强制主库

4. 高可用
   - MHA / Orchestrator
   - 自动故障切换
   - VIP 漂移
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐