1. 引言:为什么需要读写分离?
假设你经营一家外卖平台,每天有10万用户查询餐厅菜单(读操作),但只有1万用户实际下单(写操作)。如果所有请求都打到同一个数据库,就像让一个主厨同时负责炒菜和接单,效率必然低下。读写分离的核心思想是将读操作和写操作分发到不同的数据库实例,从而提升系统吞吐量。本文将通过C#和MySqlConnector驱动,手把手教你实现这一架构。
2. 读写分离的基本原理
- 主库(Master):处理所有写操作(INSERT/UPDATE/DELETE)
- 从库(Replica):通过主从复制同步数据,处理读操作(SELECT)
- 路由策略:根据SQL类型自动选择连接目标
3.为什么选择MySqlConnector?
- 官方推荐:MySQL官方推出的.NET Core驱动(替代旧版MySql.Data)
- 性能优势:异步IO支持完善,连接池管理更高效
- 扩展性:内置负载均衡和故障转移策略
4. 环境准备
MySQL Server 8.0.32(一主一从)
.NET Core 6.0
MySqlConnector 2.2.6
5. 基础实现:四步完成读写分离
5.1 配置连接字符串
// 主库连接字符串
var master = "Server=master.example.com;Database=shop;User=admin;Password=P@ssw0rd;";
// 从库连接字符串(支持多个)
var replicas = new[] {
"Server=replica1.example.com;Database=shop;User=reader;Password=Reader123;",
"Server=replica2.example.com;Database=shop;User=reader;Password=Reader123;"
};
5.2 创建连接工厂
using MySqlConnector;
var builder = new MySqlConnectionStringBuilder(master)
{
// 启用负载均衡
LoadBalance = MySqlLoadBalance.RoundRobin,
// 自动从库列表
ReplicaServers = { new ReplicaServer(replicas[0]), new ReplicaServer(replicas[1]) }
};
5.3 执行查询的示例
public async Task<List<Order>> GetUserOrders(int userId)
{
using var connection = new MySqlConnection(builder.ConnectionString);
await connection.OpenAsync();
// 自动路由到从库
var cmd = new MySqlCommand("SELECT * FROM orders WHERE user_id=@id", connection);
cmd.Parameters.AddWithValue("@id", userId);
var orders = new List<Order>();
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
orders.Add(new Order(
reader.GetInt32("id"),
reader.GetDateTime("create_time")
));
}
return orders;
}
5.4 执行写入的示例
public async Task CreateOrder(Order order)
{
using var connection = new MySqlConnection(builder.ConnectionString);
await connection.OpenAsync();
// 自动路由到主库
var cmd = new MySqlCommand("INSERT INTO orders (...) VALUES (...)", connection);
// 添加参数...
await cmd.ExecuteNonQueryAsync();
}
6. 高级配置:应对真实场景
6.1 故障转移策略
builder.RetryCount = 3; // 失败时重试次数
builder.ServerRedirectionMode = ServerRedirectionMode.Enabled; // 支持主库重定向
6.2 读写分离的例外处理
// 强制指定主库查询(适用于需要实时数据的场景)
var cmd = new MySqlCommand("SELECT * FROM orders FORCE_MASTER", connection);
7. 关联技术:提升方案健壮性
7.1 连接池优化
// 在Startup.cs中配置全局连接池
services.AddMySqlDataSource(
builder.ConnectionString,
static settings => settings.MinimumPoolSize = 10
);
7.2 与Dapper整合
public async Task<User> GetUser(int id)
{
using var connection = await _dataSource.OpenConnectionAsync();
return await connection.QueryFirstOrDefaultAsync<User>(
"SELECT * FROM users WHERE id=@id",
new { id }
);
}
8. 典型应用场景
- 高并发读场景:新闻类APP的文章浏览
- 报表分析系统:不影响线上交易的统计查询
- 微服务架构:订单服务写主库,支付服务读从库
9. 技术方案优缺点分析
优点:
- 读性能线性扩展
- 故障隔离,主库崩溃不影响读服务
- 硬件成本优化(从库可使用低配服务器)
缺点:
- 主从同步延迟(通常1-5秒)
- 事务跨库操作复杂
- 需额外维护从库状态
10. 注意事项与避坑指南
- 延迟容忍度:用户个人中心页可接受延迟,但支付状态需实时
- 事务陷阱:开启事务后所有操作强制走主库
- 连接泄露:务必使用
using
或Dispose()
释放连接 - 监控指标:重点关注
Seconds_Behind_Master
和线程池利用率
11. 总结与展望
通过MySqlConnector实现读写分离,就像给数据库装上了交通信号灯。虽然初期配置需要细心调试,但带来的性能提升是显著的。未来可结合ProxySQL实现更智能的路由,或在Kubernetes中动态扩展从库实例。