跳转至

MySQL 读写分离代码实现思路

一、架构

        /-- 主库(写)
应用 --<
        \-- 从库(读)

主库负责写,从库同步主库 Binlog,应用读走从库。

二、路由策略

核心问题:一个 SQL 发主库还是从库?

1. 静态规则

  • INSERT/UPDATE/DELETE → 主库。
  • SELECT → 从库。

2. 动态规则

  • 事务内的读走主库(避免主从延迟导致读不到刚写的数据)。
  • 强制走主库:/*+ master */ SELECT ...
  • 慢 SQL 走主库,避免从库延迟。

三、实现方式

方案 1:Spring AbstractRoutingDataSource

Spring 提供了抽象类,可以根据 key 动态切换数据源:

public class MasterSlaveDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContext.get();   // "master" / "slave"
    }
}

线程上下文保存当前路由 key:

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

用 AOP 在 Service 方法上切换:

@Aspect
@Component
public class DataSourceAspect {
    @Before("@annotation(transactional)")
    public void before(Transactional transactional) {
        DataSourceContext.master();
    }

    @After("@annotation(transactional)")
    public void after() {
        DataSourceContext.clear();
    }
}

方案 2:ShardingSphere-JDBC

成熟中间件,配置即生效:

dataSources:
  master: ...
  slave1: ...
rules:
  - !READWRITE_SPLITTING
    dataSources:
      write_ds: master
      read_ds: [slave1]

四、主从延迟问题

主从同步是异步的,写主库后立刻读从库可能读不到。

解决方法

  1. 强制读主:写操作后的短暂时间内读主库。
  2. 中间件判断:从库同步位点落后于主库多少时,读走主。
  3. 半同步复制:主库等至少一个从库收到 Binlog 才返回。
  4. 业务层容忍:大部分场景最终一致即可。

五、注意事项

  1. 事务必须走主库:否则写一半切到从库读不到。
  2. DataSourceContext.clear() 必须在 finally 里,否则线程复用导致路由错乱。
  3. 连接池隔离:主从用不同连接池,避免互相影响。
  4. 多从库负载均衡:简单轮询、权重、一致性 hash。

面试加分

  • 读写分离是应用层路由,不是 MySQL 自带的。
  • ShardingSphere / MyCat / Vitess 是常用中间件。