Skip to content

主从复制详解

一、什么是主从复制?

主从复制(Master-Slave Replication)是MySQL提供的数据同步机制,用于将主库(Master)的交易数据异步/半同步复制到从库(Slave)
核心作用

  • 读写分离:主库处理写操作(INSERT/UPDATE/DELETE),从库处理读操作(SELECT),缓解主库压力;
  • 数据备份:从库作为主库的冗余副本,避免因主库故障导致数据丢失;
  • 高可用:主库宕机时,从库可快速切换为主库,保证服务连续性;
  • 横向扩展:通过增加从库数量,提升读性能(比如应对高并发查询)。

二、主从复制的工作原理

主从复制的核心依赖3个组件3步流程,我们用“binlog日志传递+relay log重放”来概括:

1. 关键组件

  • 二进制日志(Binlog):主库生成的交易日志,记录所有修改数据的操作(如DML、DDL),是复制的“源数据”;
  • IO线程(Slave IO Thread):从库启动的线程,负责连接主库,读取主库的binlog并写入从库的中继日志(Relay Log)
  • SQL线程(Slave SQL Thread):从库启动的线程,负责读取中继日志,执行其中的SQL语句,将主库的数据变更同步到从库。

2. 复制流程(异步模式,默认)

主库(Master)                从库(Slave)
  |                            |
  | 1. 执行SQL(写操作)          |
  | 2. 写入Binlog               |
  |                            |
  |--------------------------->| 3. IO线程读取Binlog,写入Relay Log
  |                            |
  |                            | 4. SQL线程读取Relay Log,执行SQL
  |                            | 5. 数据同步完成

3. 关键细节

  • Binlog的作用:主库的所有写操作都会先写入binlog(顺序写,性能高),再修改内存中的数据(InnoDB的事务提交逻辑:redo log -> binlog -> 提交);
  • Relay Log的作用:从库的“缓冲日志”,避免直接读取主库binlog导致主库压力过大,同时支持断点续传(比如网络中断后,从上次断开的位置继续同步);
  • 异步 vs 半同步 vs 全同步
    • 异步(默认):主库写完binlog就返回客户端,不等待从库确认,性能最高但可能丢数据(比如主库宕机时,未同步的binlog丢失);
    • 半同步(Semi-Sync):主库需要等待至少一个从库收到binlog并返回确认(ack),再返回客户端,牺牲少量性能(延迟约1ms),但保证数据不丢;
    • 全同步(Full-Sync):主库需要等待所有从库都应用了binlog,再返回客户端,安全性最高但性能最差(几乎不用)。

三、主从复制的配置步骤

一主一从为例,讲解基础配置(假设主库IP:192.168.1.100,从库IP:192.168.1.200)。

1. 主库(Master)配置

步骤1:修改my.cnf(或my.ini)
开启binlog,设置主库唯一标识(server-id):

ini
[mysqld]
# 开启binlog,指定日志文件名前缀(如mysql-bin.000001)
log_bin = mysql-bin
# 主库唯一ID(必须≠从库)
server_id = 100
# binlog格式(推荐ROW,后面讲原因)
binlog_format = ROW
# 可选:只复制指定数据库(如db_test)
# binlog_do_db = db_test
# 可选:忽略指定数据库
# binlog_ignore_db = mysql

步骤2:重启主库

bash
systemctl restart mysqld  # CentOS
service mysql restart     # Ubuntu

步骤3:创建复制用户
主库需要创建一个仅用于复制的用户(授予REPLICATION SLAVE权限):

sql
CREATE USER 'repl'@'192.168.1.%' IDENTIFIED BY 'repl_password';  -- 允许从库网段连接
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.1.%';
FLUSH PRIVILEGES;  -- 刷新权限

步骤4:获取主库状态
锁表(避免同步过程中数据变更),记录binlog文件名和位置(后续从库需要用):

sql
FLUSH TABLES WITH READ LOCK;  -- 锁表(只读)
SHOW MASTER STATUS;  -- 查看主库状态

输出示例:

+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000003 | 1234     | db_test      | mysql            |
+------------------+----------+--------------+------------------+

注意:记住File(mysql-bin.000003)和Position(1234),后续从库需要用到。
解锁表(完成后执行):

sql
UNLOCK TABLES;

2. 从库(Slave)配置

步骤1:修改my.cnf
设置从库唯一标识(server-id),关闭binlog(可选,若从库不需要作为其他从库的主库):

ini
[mysqld]
# 从库唯一ID(必须≠主库)
server_id = 200
# 可选:关闭binlog(若从库不做为主库)
# skip_log_bin
# 可选:中继日志文件名前缀(默认relay-bin)
# relay_log = relay-bin

步骤2:重启从库

bash
systemctl restart mysqld

步骤3:初始化从库数据(可选但重要)
若主库已有数据,从库需要先同步主库的初始数据(否则复制会从当前binlog位置开始,导致数据不一致)。
常用方法:

  • mysqldump:适用于小数据量(如10G以内):
    bash
    # 主库导出数据(包含binlog位置)
    mysqldump -u root -p --all-databases --master-data=2 > master_data.sql
    # 从库导入数据
    mysql -u root -p < master_data.sql
  • XtraBackup:适用于大数据量(如100G以上),支持热备份(不锁表),推荐生产环境使用。

步骤4:配置从库连接主库
执行CHANGE MASTER TO命令,指定主库信息、复制用户、binlog位置:

sql
CHANGE MASTER TO
  MASTER_HOST = '192.168.1.100',  -- 主库IP
  MASTER_USER = 'repl',           -- 复制用户
  MASTER_PASSWORD = 'repl_password',  -- 复制用户密码
  MASTER_LOG_FILE = 'mysql-bin.000003',  -- 主库的binlog文件名(来自SHOW MASTER STATUS)
  MASTER_LOG_POS = 1234;          -- 主库的binlog位置(来自SHOW MASTER STATUS)

步骤5:启动复制

sql
START SLAVE;  -- 启动从库复制线程
SHOW SLAVE STATUS\G;  -- 查看从库状态

关键状态检查SHOW SLAVE STATUS输出):

  • Slave_IO_RunningYes(IO线程正常运行);
  • Slave_SQL_RunningYes(SQL线程正常运行);
  • Seconds_Behind_Master0(从库延迟主库0秒,理想状态)。

四、Binlog格式(重要)

Binlog有3种格式,直接影响复制的正确性性能

1. STATEMENT(基于语句)

  • 特点:记录执行的SQL语句(如UPDATE user SET name='xxx' WHERE id=1);
  • 优点:日志体积小,性能高;
  • 缺点可能导致主从数据不一致(如使用NOW()RAND()等函数,或存储过程中的非确定性操作);
  • 适用场景:无复杂函数或存储过程的简单业务。

2. ROW(基于行)

  • 特点:记录每行数据的变更细节(如id=1的行,nameold变为new);
  • 优点主从数据绝对一致(避免非确定性操作的问题);
  • 缺点:日志体积大(如批量更新10万行,会记录10万行的变更),性能略低;
  • 适用场景:生产环境推荐(尤其是有复杂业务逻辑的系统)。

3. MIXED(混合模式)

  • 特点:自动选择STATEMENT或ROW格式(简单语句用STATEMENT,复杂语句用ROW);
  • 优点:平衡性能和正确性;
  • 缺点:不如ROW格式稳定(某些场景仍可能出现不一致);
  • 适用场景:过渡阶段或对性能要求极高的场景。

结论生产环境优先选择ROW格式,确保数据一致性。

五、常见复制架构

根据业务需求,主从复制可以扩展为以下架构:

1. 一主一从

  • 结构:1个主库 + 1个从库;
  • 适用场景:小规模应用,用于读写分离或备份。

2. 一主多从

  • 结构:1个主库 + N个从库;
  • 适用场景:高并发读场景(如电商首页查询),通过增加从库数量提升读性能;
  • 注意:主库的binlog需要发送给所有从库,会增加主库的网络压力(可通过级联复制缓解)。

3. 级联复制(主->从->从)

  • 结构:主库(Master)→ 中间从库(Intermediate Slave)→ 下游从库(Slave);
  • 适用场景:主库网络压力大(如10个从库),中间从库作为“转发者”,减轻主库的binlog发送压力;
  • 配置:中间从库需要开启log_slave_updates(允许将从主库同步的binlog写入自己的binlog,供下游从库读取)。

4. 双主复制(互为主从)

  • 结构:两个库互为主库和从库(A是B的主库,B是A的主库);
  • 适用场景:高可用(如主库宕机后,从库自动切换为主库),或需要双向同步的场景(如多地域部署);
  • 注意:需要避免循环复制(通过server-id过滤,或使用replicate-ignore-db)。

六、常见问题与优化

1. 复制延迟(Seconds_Behind_Master 增大)

  • 原因
    • 主库写压力过大(如批量插入100万行);
    • 从库读压力过大(如从库同时处理大量查询);
    • 网络延迟(主库与从库之间网络慢);
    • SQL线程执行慢(如从库的事务需要锁表,或未开启并行复制)。
  • 解决方法
    • 优化主库:拆分大事务(如将100万行插入拆分为10次,每次10万行);
    • 优化从库:增加从库数量(分散读压力),或升级从库硬件(如更大的内存、更快的磁盘);
    • 开启并行复制:MySQL 5.7+支持基于组提交的并行复制slave_parallel_type = LOGICAL_CLOCKslave_parallel_workers = 4,设置并行线程数);
    • 使用半同步复制:减少异步复制的延迟(但会增加主库的响应时间)。

2. 复制中断(Slave_IO_Running 或 Slave_SQL_Running 为 No)

  • 常见原因
    • 从库的relay log损坏(如磁盘故障);
    • 主库的binlog被删除(如主库设置了expire_logs_days,自动删除旧binlog,而从库未同步完);
    • 从库执行SQL时出错(如主库有某张表,从库没有,导致Table not found)。
  • 解决方法
    • 重新初始化从库:若从库数据丢失或损坏,用mysqldump或XtraBackup重新同步主库数据;
    • 跳过错误事务(不推荐,可能导致数据不一致):
      sql
      STOP SLAVE;
      SET GLOBAL sql_slave_skip_counter = 1;  -- 跳过1个事务
      START SLAVE;
    • 使用GTID(全局事务标识符):避免手动指定binlog位置,自动跳过已执行的事务(后面讲GTID)。

3. 数据不一致(主从数据不同步)

  • 原因
    • Binlog格式使用STATEMENT(非确定性操作);
    • 从库执行了手动修改(如直接在从库插入数据);
    • 复制中断后未正确恢复(如跳过了关键事务)。
  • 解决方法
    • 使用ROW格式:确保主从数据一致;
    • 禁止从库写操作:从库只用于读,避免手动修改;
    • 定期校验数据:使用pt-table-checksum工具(Percona Toolkit)校验主从数据一致性,若不一致用pt-table-sync修复。

七、GTID(全局事务标识符)—— 现代复制的核心

传统复制需要手动指定主库的binlog文件名和位置(MASTER_LOG_FILEMASTER_LOG_POS),故障切换时非常麻烦(如主库宕机,需要找到从库的最后一个binlog位置,再切换到新主库)。
GTID(Global Transaction ID) 是MySQL 5.6+引入的全局唯一事务标识符,格式为:server-id:transaction-id(如100:123,表示主库100的第123个事务)。
核心优势

  • 自动同步:从库会记录已执行的GTID,主库会自动发送未执行的GTID,无需手动指定binlog位置;
  • 故障切换简单:主库宕机后,从库切换为主库,其他从库只需连接新主库,自动同步未执行的事务;
  • 避免循环复制:双主复制中,通过GTID过滤已执行的事务,避免循环执行。

GTID配置步骤(主库)

修改my.cnf:

ini
[mysqld]
server_id = 100
log_bin = mysql-bin
binlog_format = ROW
# 开启GTID
gtid_mode = ON
enforce_gtid_consistency = ON  -- 强制GTID一致性(避免非GTID事务)

GTID配置步骤(从库)

修改my.cnf:

ini
[mysqld]
server_id = 200
gtid_mode = ON
enforce_gtid_consistency = ON
# 允许从库将GTID写入自己的binlog(级联复制需要)
log_slave_updates = ON

GTID复制启动命令

从库连接主库时,无需指定MASTER_LOG_FILEMASTER_LOG_POS,只需指定MASTER_AUTO_POSITION = 1(自动获取GTID位置):

sql
CHANGE MASTER TO
  MASTER_HOST = '192.168.1.100',
  MASTER_USER = 'repl',
  MASTER_PASSWORD = 'repl_password',
  MASTER_AUTO_POSITION = 1;  -- 自动同步GTID
START SLAVE;