MySQL 读写分离是提升数据库性能和可用性的核心架构设计,通过将写操作(INSERT/UPDATE/DELETE)路由到主库(Master)、读操作(SELECT)路由到从库(Slave)实现负载均衡。以下是详细实现方案:
一、为什么需要读写分离?
场景 | 读写分离的价值 |
---|---|
读多写少(如电商、内容平台) | 80%+ 读请求分散到从库,减轻主库压力 |
高并发场景 | 水平扩展读能力,避免单点性能瓶颈 |
数据分析/报表 | 复杂查询不影响主库业务 |
灾备恢复 | 从库作为备份,主库故障时可切换 |
二、MySQL 读写分离架构图
三、四大实现方式详解
1. 应用层代码实现(直连数据库)
- 原理:在业务代码中硬编码或配置读写路由规则
- 示例(Spring Boot + MyBatis):
@Configuration public class DataSourceConfig { @Bean(name = "masterDataSource") public DataSource masterDataSource() { // 配置主库 return DataSourceBuilder.create()...build(); } @Bean(name = "slaveDataSource") public DataSource slaveDataSource() { // 配置从库 return DataSourceBuilder.create()...build(); } @Bean public AbstractRoutingDataSource routingDataSource() { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", masterDataSource()); targetDataSources.put("slave", slaveDataSource()); AbstractRoutingDataSource ds = new AbstractRoutingDataSource() { @Override protected Object determineCurrentLookupKey() { // 根据事务/注解决定数据源 return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "slave" : "master"; } }; ds.setTargetDataSources(targetDataSources); return ds; } }
- 优点:简单直接,无额外组件依赖
- 缺点:
- 侵入业务代码,维护成本高
- 无法自动感知从库故障
- 负载均衡能力弱
2. 数据库中间件代理(推荐方案)
在应用与数据库之间部署代理层,自动路由请求
中间件 | 开发方 | 特点 |
---|---|---|
MySQL Router | MySQL官方 | 轻量级,集成于MySQL生态,支持自动故障转移 |
ProxySQL | 开源社区 | 高性能(毫秒级延迟),支持动态配置加载 |
MyCAT | 阿里开源 | 支持分库分表+读写分离,适合复杂场景 |
ShardingSphere-Proxy | Apache | 生态强大,兼容多种数据库,支持分布式事务 |
ProxySQL 典型配置:
-- 定义主库
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (10, 'master', 3306);
-- 定义从库
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (20, 'slave1', 3306);
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (20, 'slave2', 3306);
-- 配置读写路由规则
INSERT INTO mysql_query_rules(active, match_pattern, destination_hostgroup)
VALUES
(1, '^SELECT.*', 20), -- 读操作路由到从库组
(1, '.*', 10); -- 其他操作路由到主库
-- 保存配置
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
3. 驱动层实现(透明化方案)
- 原理:JDBC 驱动层自动分离读写(如:ShardingSphere-JDBC)
- 示例配置:
# ShardingSphere-JDBC 配置 spring: shardingsphere: datasource: names: master,slave0,slave1 master: type: com.zaxxer.hikari.HikariDataSource jdbc-url: jdbc:mysql://master:3306/db slave0: type: com.zaxxer.hikari.HikariDataSource jdbc-url: jdbc:mysql://slave0:3306/db slave1: type: com.zaxxer.hikari.HikariDataSource jdbc-url: jdbc:mysql://slave1:3306/db rules: replica-query: data-sources: pr_ds: primary-data-source-name: master replica-data-source-names: slave0,slave1 load-balancer-name: round_robin load-balancers: round_robin: type: ROUND_ROBIN
- 优点:
- 对应用透明(无需改代码)
- 支持负载均衡算法(轮询、权重等)
- 缺点:需引入特定驱动
4. 基于DNS的读写分离
- 原理:为读/写分别配置不同DNS域名
write.example.com
→ 主库IPread.example.com
→ 从库SLB(负载均衡器)
- 优点:实现简单
- 缺点:
- DNS缓存导致故障切换延迟
- 无法根据SQL类型精细路由
- 已逐渐被中间件方案取代
四、关键问题与解决方案
1. 主从延迟导致数据不一致
- 场景:用户下单后立即查询订单,从库尚未同步
- 解决方案:
- 强制读主库:对一致性要求高的操作添加注解(如
@Master
) - GTID 等待:执行读操作前等待指定GTID同步(需业务改造)
- 半同步复制:确保至少一个从库收到binlog才返回成功(
rpl_semi_sync_master_wait_for_slave_count=1
)
- 强制读主库:对一致性要求高的操作添加注解(如
2. 事务中的读写分离
- 问题:事务内混合读写操作时强制路由到主库
- 方案:
- 开启事务时自动使用主库连接
- 通过注解标记只读事务(Spring
@Transactional(readOnly = true)
)
3. 从库故障处理
- 健康检查:中间件定期探测从库状态
- 自动摘除:故障从库被移出连接池(如ProxySQL的
mysql-monitor
)
五、方案选型建议
场景 | 推荐方案 | 理由 |
---|---|---|
中小项目,快速落地 | 应用层代码实现 | 简单,无需部署额外组件 |
高并发生产环境 | ProxySQL/MySQL Router | 高性能,自动故障转移 |
微服务+分库分表 | ShardingSphere-JDBC | 一体化解决方案,生态完善 |
云数据库(如RDS) | 云厂商代理服务 | 免运维,深度集成(如阿里云Proxy) |
六、最佳实践
- 从库读负载均衡:
- 轮询(Round Robin):默认均分流量
- 权重分配:高性能从库承担更多流量
- 连接池配置:
# HikariCP 示例 maximumPoolSize=20 # 避免连接数超过数据库限制 connectionTimeout=3000
- 监控告警:
- 主从延迟监控(
SHOW SLAVE STATUS
→Seconds_Behind_Master
) - 代理层性能指标(QPS、连接数、延迟)
- 主从延迟监控(
- 灰度发布:
- 先切部分读流量到新从库观察
- 使用中间件的权重调整功能逐步迁移
💡 终极建议:
对于核心业务,ProxySQL + 多从库是最稳健的方案;对于云环境,直接使用云数据库的读写分离功能(如AWS RDS Proxy)可大幅降低运维复杂度。