3、第一次裂变:读写分离——把查询和写入分开,数据库不再打架
好,咱们进入正题。
上一章我们聊了单库单表,那是所有系统的起点。但说实话,那个阶段撑不了多久。一旦你的用户量上来,第一个扛不住的就是数据库。我见过太多团队,业务刚有点起色,数据库先挂了。
为什么会这样?
因为读写混在一起,就像一条双向两车道,进城和出城的车都挤在一起。早晚高峰,谁也走不动。
3.1 问题本质:锁竞争与资源争抢
单库模式下,所有的查询和写入都在同一个数据库实例里。你想想看,一条 INSERT 语句正在写数据,另一条 SELECT 语句想读同一行——数据库就得加锁、排队。
我在项目中遇到过最典型的情况:一个电商系统的订单表,白天高峰期每秒有几百笔订单写入。这时候运营后台跑一个统计报表,一个慢查询就把整个库拖死了。前台用户下单超时,后台报表也出不来,两边都骂娘。
说白了,问题就两个:
- 锁竞争:写操作要加锁,读操作要等待,互相阻塞。
- 资源争抢:CPU、内存、磁盘IO,读写都在抢同一份资源。
核心结论:读写分离不是锦上添花,而是系统活过第一轮用户增长的必要手段。
3.2 架构方案:一主多从,各司其职
读写分离的架构,说白了就是一句话:写库(Master)负责写入,读库(Slave)负责查询。
典型的部署模式是这样的:
- 1个主库:只处理
INSERT、UPDATE、DELETE操作。 - N个从库:只处理
SELECT查询操作。 - 主从同步:主库把变更日志(binlog)同步给从库。
嗯,这里要注意:从库的数据是「最终一致」的,不是强一致的。因为同步有延迟。
我个人的习惯是,在业务代码里显式区分数据源。举个例子:
// 伪代码:读写分离的数据源路由
public class DataSourceRouter {
public DataSource getDataSource(String operationType) {
if ("write".equals(operationType)) {
return masterDataSource; // 写操作走主库
} else {
// 读操作:从多个从库中随机选一个
return slaveDataSources[random.nextInt(slaveDataSources.length)];
}
}
}
// 使用示例
public class OrderService {
public void createOrder(Order order) {
DataSource ds = router.getDataSource("write");
// 执行 INSERT 语句
}
public Order getOrder(Long orderId) {
DataSource ds = router.getDataSource("read");
// 执行 SELECT 语句
}
}
避坑指南:我曾经在项目里把所有查询都扔到从库,结果发现刚创建的订单查不到。原因就是主从同步延迟。解决方案:对于「必须读到最新数据」的场景,强制走主库查询。
3.3 主从同步:binlog 与延迟问题
主从同步的核心机制,是 MySQL 的 binlog(二进制日志)。
流程大致是这样的:
- 主库执行完写操作,把变更记录写入 binlog。
- 从库的 I/O 线程拉取 binlog,写入本地的 relay log(中继日志)。
- 从库的 SQL 线程读取 relay log,重放变更。
这个过程是异步的。所以延迟是必然的,只是多少的问题。
我见过一个极端案例:某团队做活动,主库瞬间写入量暴增,从库同步跟不上,延迟飙到了 30 秒。结果用户看到的页面数据全是旧的,活动库存显示有货,点进去却提示已售罄。
警告:主从延迟不是 bug,是特性。你必须接受它,并在业务层面做补偿。比如:
- 关键数据(支付结果、订单状态)强制走主库查询。
- 非关键数据(历史记录、统计报表)可以接受延迟。
- 设置监控告警,延迟超过阈值时自动切换。
3.4 实战落地:从单库到读写分离的改造步骤
如果你现在还是单库,想改成读写分离,我建议按这个步骤来:
- 搭建从库:先给主库配一个从库,开启 binlog,配置同步。
- 验证同步:在主库上执行写操作,确认从库能正确同步数据。
- 改造代码:在数据访问层增加路由逻辑,区分读写操作。
- 灰度切换:先把 10% 的读流量切到从库,观察一段时间。
- 全量切换:确认没问题后,把所有读流量切到从库。
- 持续监控:关注主从延迟、从库负载、查询性能等指标。
我个人习惯在改造初期,保留一个「兜底开关」。万一从库出问题,可以一键切回主库。这个开关救过我两次,真的。
3.5 读写分离的适用场景与局限
读写分离不是万能的。它最适合的场景是:
- 读多写少:比如内容管理系统、论坛、博客。
- 查询压力大:报表系统、数据分析后台。
- 写操作相对稳定:写入量不大,主要是读压力。
但如果你的场景是写多读少,或者读写比例接近 1:1,那读写分离的效果就有限了。因为主库的写入压力并没有减轻,只是把读压力分散了。
另外,还有一个容易被忽略的问题:从库的硬件配置。很多团队给从库配的机器比主库差,结果从库扛不住查询压力,反而成了瓶颈。我建议从库的配置至少和主库持平,甚至更高。
一句话总结:读写分离是架构演进的第一道坎,跨过去,你的系统就能撑住第一波用户增长。但别高兴太早,后面还有更多挑战等着你。
下一章,我们会聊缓存。那是比读写分离更狠的一招——直接把数据塞进内存,让数据库彻底「躺平」。