一、开篇故事:图书馆的分馆同步 📚
想象你管理一个图书馆系统,总馆和分馆要保持图书同步:
模式1:异步复制(快递邮寄)📦
总馆:"我新进了10本书。"
分馆:"好的,记下了,晚点再上架。"
总馆:"OK,我继续接待读者。"
读者:"我要借刚进的新书。"
总馆:"可以!" ✅
分馆:"还没到呢..." ❌(延迟)
特点:
- 总馆不等分馆,效率高
- 但分馆会延迟
模式2:半同步复制(快递确认)📋
总馆:"我新进了10本书。"
分馆1:"收到!"
总馆:"好,至少1个分馆收到了,我放心了。"
分馆2、3:"我们还在路上..." 🚚
特点:
- 至少1个分馆确认
- 比异步可靠
模式3:同步复制(实时同步)⚡
总馆:"我新进了10本书。"
分馆1、2、3:"都上架好了!"
总馆:"好,我才能继续。"
读者:"借新书!"
总馆、分馆:"都有!" ✅✅✅
特点:
- 完全同步,无延迟
- 但总馆效率低
这就是MySQL主从复制的三种模式!
二、主从复制原理 🎯
2.1 主从架构
主库(Master)
↓ binlog
┌──────┴──────┬──────┐
↓ ↓ ↓
从库1(Slave) 从库2 从库3
↓
应用读取
2.2 复制流程(三步走)
步骤1:主库记录binlog
主库执行:INSERT INTO users VALUES (1, '张三');
写入binlog:position=1234
步骤2:从库IO线程读取binlog
从库IO线程:"主库,给我最新的binlog!"
主库Dump线程:"给你!position 1234..."
从库IO线程:写入relay log(中继日志)
步骤3:从库SQL线程执行relay log
从库SQL线程:读取relay log
从库SQL线程:执行SQL
从库:INSERT INTO users VALUES (1, '张三');
完成!主从数据一致!✅
2.3 详细图解
主库(Master)
↓ 执行SQL
[binlog]
↓
Dump线程(发送binlog)
↓ 网络传输
从库(Slave)
↓
IO线程(接收binlog)
↓ 写入
[relay log]
↓ 读取
SQL线程(执行SQL)
↓
从库数据库
2.4 关键组件
| 组件 | 位置 | 作用 |
|---|---|---|
| binlog | 主库 | 记录所有修改操作 |
| Dump线程 | 主库 | 发送binlog给从库 |
| IO线程 | 从库 | 接收binlog,写入relay log |
| relay log | 从库 | 中继日志,存储binlog副本 |
| SQL线程 | 从库 | 读取relay log,执行SQL |
三、三种复制模式详解 🔍
3.1 异步复制(Async Replication)
流程:
主库:
1. 执行SQL
2. 写binlog
3. 返回成功 ← 不等从库
从库:
1. IO线程拉取binlog(可能延迟)
2. SQL线程执行(可能更延迟)
特点:
✅ 性能最高(主库不等待)
❌ 可能丢数据(主库宕机,从库还没同步)
❌ 延迟问题(从库可能落后几秒甚至几分钟)
适用场景:
- 读多写少
- 对数据一致性要求不高
- 追求性能
3.2 半同步复制(Semi-Sync Replication)
流程:
主库:
1. 执行SQL
2. 写binlog
3. 等待至少1个从库确认接收binlog ← 等待!
4. 返回成功
从库:
1. IO线程接收binlog,写入relay log
2. 发送ACK给主库 ← 确认
3. SQL线程异步执行(主库不等)
图解:
主库 从库1 从库2
| | |
|---(1) 写binlog-------->| |
| | |
|<--(2) ACK(收到)------| |
| | |
|---(3) 返回成功给客户端 |
| | |
|----------------------->|----------------->|
从库2可能还没收到(但不影响)
配置:
-- 主库开启半同步
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; -- 1秒超时
-- 从库开启半同步
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
SET GLOBAL rpl_semi_sync_slave_enabled = 1;
特点:
✅ 比异步可靠(至少1个从库确认)
✅ 性能可接受(只等IO线程,不等SQL线程)
⚠️ 从库宕机,主库会退化为异步
❌ 仍有延迟(SQL线程异步执行)
3.3 同步复制(Sync Replication)
MySQL本身不支持真正的同步复制,但可以通过组复制(Group Replication)实现
流程:
主库:
1. 执行SQL
2. 写binlog
3. 等待所有从库执行完成 ← 等很久!
4. 返回成功
特点:
✅ 完全一致,无延迟
✅ 数据最安全
❌ 性能最差(等待所有从库)
❌ 不适合生产环境
四、主从延迟问题 ⏰
4.1 什么是主从延迟?
定义: 从库的数据落后主库的时间差。
查看延迟:
-- 在从库执行
SHOW SLAVE STATUS\G
-- 关键字段
Seconds_Behind_Master: 5 ← 延迟5秒
4.2 延迟的危害
-- 主库
INSERT INTO users VALUES (100, '张三');
COMMIT; -- 返回成功
-- 应用立即从从库查询
SELECT * FROM users WHERE id = 100;
-- 结果:NULL ❌(从库还没同步)
-- 5秒后再查
SELECT * FROM users WHERE id = 100;
-- 结果:张三 ✅(同步完成)
真实案例:
电商下单流程:
1. 用户下单 → 写主库 ✅
2. 立即跳转订单详情页 → 读从库 ❌ 找不到订单
3. 用户:"我的订单呢?!" 😱
4. 5秒后刷新 → 从库同步完成 ✅ 订单出现
5. 用户:"???" 🤔
4.3 延迟的原因
原因1:从库单线程执行(MySQL 5.6之前)
主库:10个线程并发写入 → binlog
从库:1个SQL线程串行执行 → 慢!
解决: 并行复制(MySQL 5.7+)
原因2:大事务
-- 主库执行大事务
BEGIN;
UPDATE users SET status = 1; -- 更新100万行
DELETE FROM logs WHERE id < 1000000; -- 删除100万行
COMMIT; -- 耗时10秒
-- 从库也要执行10秒!
-- 这10秒内,从库延迟至少10秒!
解决: 拆分大事务
原因3:从库负载高
从库同时承担:
- 同步主库数据
- 处理大量查询请求
→ CPU、IO资源不足
→ 同步变慢
→ 延迟增大
解决: 增加从库,分散读压力
原因4:网络延迟
主库(北京) → binlog → 从库(上海)
网络延迟:50ms
每条SQL都要传输 → 累积延迟
解决:
- 主从部署在同一机房
- 使用专线
原因5:binlog格式(ROW vs STATEMENT)
-- STATEMENT格式
UPDATE users SET score = score + 1 WHERE city = '北京';
-- binlog记录:UPDATE语句
-- 从库执行:同样的UPDATE(如果数据量大,很慢)
-- ROW格式
UPDATE users SET score = score + 1 WHERE city = '北京';
-- binlog记录:每一行的变化(1000行就记录1000条)
-- binlog变大,传输慢 → 延迟
五、解决主从延迟的方案 💡
方案1:并行复制(MySQL 5.7+)
原理: 从库多线程并行执行binlog
-- 配置并行复制
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
SET GLOBAL slave_parallel_workers = 8; -- 8个线程
效果:
单线程:执行时间10秒
8线程:执行时间1.5秒(提升6-7倍)✅
方案2:避免大事务
-- ❌ 不好:大事务
BEGIN;
UPDATE users SET status = 1; -- 100万行
COMMIT;
-- ✅ 好:拆分小事务
FOR i IN 1..100 LOOP
BEGIN;
UPDATE users SET status = 1 LIMIT 10000;
COMMIT;
END LOOP;
方案3:读写分离策略优化
// ❌ 不好:刚写完就读从库
userService.createUser(user); // 写主库
User savedUser = userService.getUser(user.getId()); // 读从库 → 可能读不到
// ✅ 好:写完读主库
@Transactional
public void createAndGet(User user) {
userService.createUser(user); // 写主库
User savedUser = userService.getUserFromMaster(user.getId()); // 读主库
}
// ✅ 好:延迟读取
userService.createUser(user); // 写主库
Thread.sleep(100); // 等待100ms
User savedUser = userService.getUser(user.getId()); // 读从库
// ✅ 最好:强制主库读
public User getUser(Long id, boolean forceMaster) {
if (forceMaster) {
return masterMapper.selectById(id); // 读主库
}
return slaveMapper.selectById(id); // 读从库
}
方案4:监控延迟,动态路由
@Service
public class DataSourceRouter {
public User getUser(Long id) {
// 检查从库延迟
int delay = slaveService.getDelaySeconds();
if (delay > 5) {
// 延迟超过5秒,读主库
return masterMapper.selectById(id);
}
// 延迟小,读从库
return slaveMapper.selectById(id);
}
}
方案5:使用缓存
@Service
public class UserService {
@Cacheable(value = "user", key = "#id")
public User getUser(Long id) {
// 先查缓存,缓存没有再查数据库
return userMapper.selectById(id);
}
@CacheEvict(value = "user", key = "#user.id")
public void updateUser(User user) {
userMapper.updateById(user);
// 更新后清除缓存
}
}
六、主从复制配置实战 🛠️
6.1 主库配置
# my.cnf
[mysqld]
# 开启binlog
log-bin=mysql-bin
server-id=1 # 主库ID,唯一
# binlog格式
binlog-format=ROW # 推荐ROW
# binlog过期时间(天)
expire_logs_days=7
# 半同步配置(可选)
rpl_semi_sync_master_enabled=1
rpl_semi_sync_master_timeout=1000 # 1秒超时
创建复制用户:
-- 主库创建复制账号
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
-- 查看主库状态
SHOW MASTER STATUS;
-- 记录File和Position
6.2 从库配置
# my.cnf
[mysqld]
server-id=2 # 从库ID,唯一,不能与主库相同
# 半同步配置(可选)
rpl_semi_sync_slave_enabled=1
# 并行复制(MySQL 5.7+)
slave_parallel_type=LOGICAL_CLOCK
slave_parallel_workers=8
# relay log
relay-log=relay-bin
配置主从关系:
-- 从库配置
CHANGE MASTER TO
MASTER_HOST='192.168.1.100',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001', -- 主库的File
MASTER_LOG_POS=154; -- 主库的Position
-- 启动从库复制
START SLAVE;
-- 查看从库状态
SHOW SLAVE STATUS\G
-- 关键字段:
-- Slave_IO_Running: Yes ← IO线程运行中
-- Slave_SQL_Running: Yes ← SQL线程运行中
-- Seconds_Behind_Master: 0 ← 无延迟
七、主从切换(故障转移)🔄
7.1 主库宕机场景
主库💀 → 从库1升级为主库
步骤:
1. 停止所有从库复制
2. 选择一个从库升级为主库
3. 其他从库指向新主库
4. 应用切换到新主库
7.2 手动切换
-- 1. 从库1:停止复制
STOP SLAVE;
-- 2. 从库1:升级为主库
RESET MASTER;
-- 3. 从库2、3:指向新主库(从库1)
STOP SLAVE;
CHANGE MASTER TO
MASTER_HOST='新主库IP',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;
START SLAVE;
-- 4. 应用切换数据源
7.3 自动切换(MHA、Orchestrator)
MHA(Master High Availability):
功能:
1. 监控主库健康状态
2. 自动故障检测
3. 自动选举新主库
4. 自动切换
5. 数据补偿(确保数据不丢失)
八、面试高频问题 🎤
Q1: MySQL主从复制的原理是什么?
答: 三步走:
- 主库记录binlog:所有修改操作记录到binlog
- 从库IO线程拉取binlog:写入relay log
- 从库SQL线程执行relay log:重放SQL,实现同步
Q2: 异步、半同步、同步复制的区别?
答:
- 异步:主库不等从库,性能高但可能丢数据
- 半同步:主库等至少1个从库确认接收binlog(不等执行),平衡性能和可靠性
- 同步:主库等所有从库执行完成,最可靠但性能差
Q3: 主从延迟的原因和解决方案?
答: 原因:
- 从库单线程执行(老版本)
- 大事务
- 从库负载高
- 网络延迟
解决:
- 并行复制(MySQL 5.7+)
- 拆分大事务
- 增加从库,分散压力
- 写完读主库或使用缓存
Q4: 如何判断主从是否同步?
答:
SHOW SLAVE STATUS\G
关键字段:
- Slave_IO_Running: Yes(IO线程正常)
- Slave_SQL_Running: Yes(SQL线程正常)
- Seconds_Behind_Master: 0(无延迟)
Q5: 主库宕机如何处理?
答:
- 手动切换:选一个从库升级为主库
- 自动切换:使用MHA、Orchestrator等工具
- 确保数据不丢失:半同步复制 + 数据补偿
九、总结口诀 📝
主从复制三步走,
binlog、relay、SQL。
主库记录binlog,
从库拉取并执行。
异步模式性能高,
半同步更可靠。
同步复制太慢了,
生产环境少用到。
主从延迟是大坑,
原因要分清。
大事务要拆分,
并行复制来帮忙。
写完立即读主库,
或者用缓存挡。
监控延迟很重要,
动态路由更智能!
参考资料 📚
下期预告: 144-MySQL的binlog、redolog、undolog的作用和区别 📝
编写时间:2025年
作者:技术文档小助手 ✍️
版本:v1.0
愿你的主从永远同步! 🔄✨