主从复制
一、主从复制架构
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 | 简化复制管理、自动定位、方便故障切换 |