Skip to content

分库分表方案(结合第三方组件) ​

概述 ​

随着数据量增长,单表或单数据库会遇到性能瓶颈。分库分表(Sharding)是将数据水平拆分到多个数据库或表的策略,以提升系统可扩展性和性能。

核心挑战 ​

  • 路由策略: 如何确定数据存储在哪个分片?
  • 跨分片查询: 如何高效查询所有分片的数据?
  • 分布式事务: 如何保证跨分片操作的一致性?
  • 数据迁移: 如何在不停机的情况下重新分片?

方案 1: ShardingCore (推荐) ​

ShardingCore 是专为 EF Core 设计的分片库。

安装 ​

bash
dotnet add package ShardingCore

配置分片路由 ​

csharp
public class OrderVirtualRoute : AbstractSimpleShardingModKeyStringVirtualTableRoute<Order>
{
    public override void Configure(EntityMetadataTableBuilder<Order> builder)
    {
        builder.ShardingProperty(o => o.UserId); // 按 UserId 分片
    }
    
    public override ModKeyConfig GetModKeyConfig()
    {
        return new ModKeyConfig { Mod = 10 }; // 10 个分片表
    }
}

注册服务 ​

csharp
builder.Services.AddShardingDbContext<AppDbContext>((config, options) =>
{
    config.UseSqlServer(connectionString);
})
.AddDefaultSharding();

app.UseSharding(); // 启动时初始化

使用 ​

csharp
// 自动路由到正确的分片
var orders = await _context.Orders
    .Where(o => o.UserId == userId)
    .ToListAsync();

方案 2: 手动实现分片 ​

分片解析器 ​

csharp
public interface IShardResolver
{
    string ResolveShard(string shardingKey);
}

public class HashShardResolver : IShardResolver
{
    private readonly int _shardCount;
    
    public HashShardResolver(int shardCount = 10)
    {
        _shardCount = shardCount;
    }
    
    public string ResolveShard(string shardingKey)
    {
        var hash = Math.Abs(shardingKey.GetHashCode());
        var shardIndex = hash % _shardCount;
        return $"Shard_{shardIndex:D2}";
    }
}

分片 DbContext Factory ​

csharp
public class ShardedDbContextFactory
{
    private readonly IShardResolver _resolver;
    private readonly IConfiguration _config;
    
    public AppDbContext CreateForShard(string shardingKey)
    {
        var shardName = _resolver.ResolveShard(shardingKey);
        var connectionString = _config.GetConnectionString(shardName);
        
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseSqlServer(connectionString)
            .Options;
        
        return new AppDbContext(options);
    }
}

仓储封装 ​

csharp
public class ShardedOrderRepository
{
    private readonly ShardedDbContextFactory _factory;
    
    public async Task AddAsync(Order order)
    {
        using var context = _factory.CreateForShard(order.UserId);
        context.Orders.Add(order);
        await context.SaveChangesAsync();
    }
    
    public async Task<List<Order>> GetByUserIdAsync(string userId)
    {
        using var context = _factory.CreateForShard(userId);
        return await context.Orders
            .Where(o => o.UserId == userId)
            .ToListAsync();
    }
}

方案 3: 读写分离 ​

主从复制架构 ​

         ┌──────────┐
         │  Master  │ ← 写入
         └────┬─────┘
              │
      ┌───────┼───────┐
      ▼       ▼       ▼
   Slave1  Slave2  Slave3 ← 读取

实现读写分离 ​

csharp
public class ReadWriteSplittingContext : DbContext
{
    private readonly IConfiguration _configuration;
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (IsReadOperation())
        {
            // 随机选择一个从库
            var slaveConnection = GetRandomSlaveConnection();
            optionsBuilder.UseSqlServer(slaveConnection);
        }
        else
        {
            // 使用主库
            var masterConnection = _configuration.GetConnectionString("Master");
            optionsBuilder.UseSqlServer(masterConnection);
        }
    }
    
    private bool IsReadOperation()
    {
        // 检查调用堆栈判断是否为读操作
        var stackTrace = new StackTrace();
        return stackTrace.ToString().Contains("Get") || 
               stackTrace.ToString().Contains("Query");
    }
}

最佳实践 ​

✅ 推荐做法 ​

  1. 选择合适的分片键: 常用查询字段、高基数字段
  2. 避免跨分片JOIN: 尽量在应用层组装数据
  3. 监控分片均衡: 定期检查各分片数据量
  4. 预留扩展空间: 设计时考虑未来扩容

❌ 避免的陷阱 ​

  1. 不要过度分片: 小数据量不需要分片
  2. 不要忽略分布式事务: 使用 Saga 或 TCC 模式
  3. 不要忘记索引: 每个分片都需要独立索引

总结 ​

方案适用场景复杂度灵活性
ShardingCore中小型项目⭐⭐ 低⭐⭐⭐ 高
手动实现定制化需求⭐⭐⭐ 高⭐⭐⭐⭐⭐ 最高
中间件(MyCat)大型分布式系统⭐⭐⭐⭐ 很高⭐⭐⭐⭐ 高

根据业务规模和团队技术能力选择合适的方案!

基于 MIT 许可发布