数据库读写分离怎么做?从主从复制到应用层路由的完整配置

读写分离本身不产生数据同步,它只是把写操作发给主库、读操作发给从库。
真正让从库有数据可读的是主从复制。
所以做读写分离前,必须先确认主从复制已经正常工作,否则读请求会读到旧数据甚至空结果。

下面以 MySQL 8.0 和 Spring Boot 项目为例,从环境准备、主从复制检查、应用层路由配置到延迟避坑,给出可以照着做的步骤。
如果你还没搭主从复制,需要先完成主库开启 binlog、从库配置复制账号并启动复制。

先确认主从复制状态

在从库执行 SHOW SLAVE STATUS\G(MySQL 8.0.22 起也可用 SHOW REPLICA STATUS\G),重点看两个字段:

  • Slave_IO_Running: Yes 和 Slave_SQL_Running: Yes,两者都必须是 Yes。
  • Seconds_Behind_Master 表示从库落后主库的秒数,0 或接近 0 才适合承载实时性要求高的读请求。

如果复制线程不是 Yes,先解决主从复制问题,不要急着配读写分离。

准备两个数据源

在 Spring Boot 项目里,读写分离通常用动态数据源实现。
准备主库和从库的连接信息,例如:

spring:
  datasource:
    master:
      url: jdbc:mysql://192.168.1.10:3306/appdb?useSSL=false&serverTimezone=Asia/Shanghai
      username: app_rw
      password: 主库密码
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave:
      url: jdbc:mysql://192.168.1.11:3306/appdb?useSSL=false&serverTimezone=Asia/Shanghai
      username: app_ro
      password: 从库密码
      driver-class-name: com.mysql.cj.jdbc.Driver

主库账号需要写权限,从库账号建议只给 SELECT 权限,降低误写从库的风险。

配置动态数据源与路由

核心是继承 AbstractRoutingDataSource,用 ThreadLocal 保存当前线程要用的数据源 key。
默认走主库,只有明确标记读的方法才切到从库。

public class RoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContext.get();
    }
}

public class DataSourceContext {
    private static final ThreadLocal KEY = new ThreadLocal<>();
    public static void set(String key) { KEY.set(key); }
    public static String get() { return KEY.get(); }
    public static void clear() { KEY.remove(); }
}

再通过配置类把 master 和 slave 两个数据源注册进去,默认目标数据源设为 master。
然后用 AOP 在方法上标记读操作:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {}

@Aspect
@Component
public class ReadOnlyAspect {
    @Around("@annotation(readOnly)")
    public Object around(ProceedingJoinPoint pjp, ReadOnly readOnly) throws Throwable {
        DataSourceContext.set("slave");
        try {
            return pjp.proceed();
        } finally {
            DataSourceContext.clear();
        }
    }
}

在查询方法上加上 @ReadOnly 即可。
注意必须在 finally 里清理 ThreadLocal,否则线程池复用时会串数据源。

主从延迟怎么处理

读写分离最常见的坑是“写完立刻读不到”。
比如用户下单后马上跳转到订单详情,写操作在主库,读请求到了从库,从库还没同步完,就会显示旧数据。

处理方式按场景选择:

  • 对实时性要求高的读,比如支付结果、订单详情,强制走主库,不标 @ReadOnly。
  • 对实时性要求不高的列表、报表,走从库。
  • 如果业务必须读从库又要保证一致性,可以在写后短暂等待,或使用半同步复制降低延迟,但半同步复制会增加写响应时间,需要评估。

另外,事务内不要随意切换数据源。
同一个事务里先写主库再读从库,读到的可能不是本事务未提交的数据。
建议事务内统一走主库。

验证读写是否真的分离

配置完成后,用两种方式验证:

  1. 在从库执行 SHOW PROCESSLIST,观察应用连接是否打到从库。
  2. 在代码里临时打印当前数据源 key,确认读方法走 slave、写方法走 master。

更直接的办法是在主库和从库分别插入一条只有主库有的测试数据,然后调用读接口,看能否读到。
如果读不到,检查 AOP 是否生效、数据源 key 是否拼写正确、从库复制是否正常。

读写分离能分担读压力,但它不解决单表过大和写入瓶颈。
如果写请求已经是瓶颈,需要从分库分表或业务拆分入手,而不是继续加从库。

分享到:
上一篇
浏览器缓存怎么设置:Nginx 与宝塔的 Cache-Control 配置和验证
下一篇
MySQL 连接数满了怎么处理?先救急再调参的完整步骤
1
系统公告

关于香港云优质服务器IP地址更换的通知

尊敬的客户: 因原IP运营商大幅涨价,为避免成本转嫁至用户,我司决定更换IP服务商。本次更换后,您的续费价格将保持不变,且新IP网络质量与服务标准不受影响。新IP已分配至后台,即刻生效(更换的新IP都是香港原生IPv4)。 涉及 IP 段: 45.192.x.x 旧 IP 停用时间: 2026 年 12 月 1 日
服务中心
客服
在线客服
24小时为您服务
咨询
联系我们
联系我们,为您的业务提供专属服务。
24/7 技术支持
如果您遇到寻求进一步的帮助,请过工单与我们进行联系。
24/7 即时支持
泽御云
售前客服
泽御云
泽御云
售后客服
泽御云
技术支持
评价
您对当前页面的整体感受是否满意?
😞
非常不满意
😕
不满意
😐
一般
🙂
满意
😊
非常满意