什么是“读写分离”?
定义:
读写分离 是一种数据库架构优化策略,核心思想是将数据库的“写操作”(INSERT/UPDATE/DELETE)和“读操作”(SELECT)分别路由到不同的数据库实例上,以提升系统整体的并发处理能力和可用性。
目的:
- 减轻主库压力
- 提高读并发能力
- 增强系统扩展性
架构示意图:
| |
写操作 → 主库
读操作 → 从库(可多个,负载均衡)
什么是“主从同步”?
定义:
主从同步(Replication)是指主库的数据变更(binlog)被自动复制到一个或多个从库,从而保持从库数据与主库“最终一致”。
同步流程(以 MySQL 为例):
- 主库执行写操作,记录到 binlog(二进制日志)
- 从库的 I/O 线程连接主库,请求 binlog,写入本地 relay log(中继日志)
- 从库的 SQL 线程读取 relay log,重放 SQL 语句,更新本地数据
注意:这个过程是异步的(默认),也就是说,主库提交事务后,不会等待从库执行完毕。
主从同步延迟问题(Replication Lag)
什么是“延迟”?
主库写入数据后,从库未能立即同步到最新状态,导致从库读取到的是“旧数据”。
举例:用户刚下单成功(写入主库),立即刷新页面查看订单(读从库),却看不到订单 —— 这就是典型的“主从延迟导致数据不一致”。
延迟产生的原因
| 原因 | 说明 |
|---|---|
| 1. 从库性能瓶颈 | CPU、磁盘 IO、内存不足,SQL 线程执行慢 |
| 2. 大事务/长事务 | 一个事务包含大量操作,从库需逐条重放,耗时久 |
| 3. 主库写入压力大 | binlog 产生速度 > 从库消费速度,积压 relay log |
| 4. 网络延迟 | 主从之间网络带宽不足或延迟高 |
| 5. 从库单线程重放(MySQL 5.6 之前) | SQL 线程单线程执行,无法并行(MySQL 5.7+ 支持并行复制) |
| 6. DDL 操作或无主键表 | ALTER TABLE、无主键 UPDATE/DELETE 导致全表扫描,拖慢同步 |
解决方案(应对主从延迟)
优化从库性能
- 升级硬件(SSD、更多 CPU、更大内存)
- 优化从库参数(如
innodb_flush_log_at_trx_commit=2,sync_binlog=0—— 牺牲部分一致性换性能) - 关闭从库的 binlog(如果从库不作为其他从库的主库)
启用并行复制(MySQL 5.7+ / MariaDB 10.0+)
MySQL 5.7 开始支持 基于事务组提交的并行复制(logical clock),可大幅提升重放速度。
| |
注意:表必须有主键或唯一索引,否则并行复制可能退化为串行。
业务层“强制读主”策略
对“写后立即读”的场景,强制走主库,避免读到旧数据。
实现方式:
- 代码标记:在写操作后,设置一个“读主标记”,在读操作时检查该标记
- 中间件路由:如 ShardingSphere、MyCat、ProxySQL,支持 hint 或注解路由
- 短时读主:写入后 1~2 秒内读主,之后读从(需结合业务容忍度)
示例(伪代码):
| |
监控 + 自动降级
- 监控主从延迟(
SHOW SLAVE STATUS→Seconds_Behind_Master) - 当延迟 > 阈值(如 5s),自动将读请求切回主库
- 延迟恢复后,再切回从库
可结合 Prometheus + AlertManager + 自动化脚本实现。
使用“半同步复制”(Semi-Sync Replication)
MySQL 支持 半同步复制:主库 commit 前,至少等待一个从库 ack 接收到 binlog。
| |
优点:降低“数据丢失”风险,一定程度减少“读不到最新数据”
缺点:写入性能下降(需等待网络往返),不能完全消除延迟(从库执行仍需时间)
采用“最终一致性” + 业务容忍设计
- 不是所有场景都需要“强一致”
- 比如:用户发评论 → 刷新页面可能看不到 → 提示“内容稍后显示”
- 或者前端轮询、WebSocket 推送更新
适合:非核心路径、展示型数据、异步操作
引入缓存层(如 Redis)缓解读压力
- 写主库后,同时更新/删除缓存
- 读请求优先读缓存,缓存无则读从库
- 可避免大量读请求直接压到数据库
注意缓存与数据库一致性问题(Cache Aside Pattern)
实际案例:电商“下单后查订单”
问题:
用户下单 → 页面跳转“我的订单” → 从库未同步 → 显示“空”
解决方案组合:
- 写后强制读主 2 秒(覆盖 99% 场景)
- 前端 loading + 重试机制(优雅降级)
- 监控延迟,自动切读主(兜底)
- 订单数据写入 Redis 缓存,读请求优先读缓存
如何监控主从延迟?
MySQL 命令:
| |
重点关注:
Seconds_Behind_Master:从库落后主库的秒数(不完全准确,仅作参考)Slave_IO_Running/Slave_SQL_Running:是否正常运行Last_IO_Error/Last_SQL_Error:错误信息
更精准方式:
- 在主库写入带时间戳的“心跳表”,从库读取计算真实延迟
- 使用
pt-heartbeat(Percona 工具)精准测量延迟
总结:主从延迟应对策略速查表
| 场景 | 推荐方案 |
|---|---|
| 写后立即读 | 强制读主(2 秒内) |
| 高并发读、允许延迟 | 读从库 + 缓存 |
| 要求数据强一致 | 半同步复制 + 读主 |
| 延迟过大自动保护 | 监控 + 自动切主 |
| 性能瓶颈 | 并行复制 + 硬件升级 + SQL 优化 |
| 架构演进 | 考虑分库分表、NewSQL(TiDB)、读写分离中间件 |